diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4831225f9..d67bb525c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,316 +1,321 @@ <?php /** * This file is automatically generated. Use 'phutil_mapper.php' to rebuild it. * @generated */ phutil_register_library_map(array( 'class' => array( + 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontController' => 'aphront/controller', 'AphrontDatabaseConnection' => 'storage/connection/base', 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', 'AphrontDefaultApplicationController' => 'aphront/default/controller', 'AphrontDialogResponse' => 'aphront/response/dialog', 'AphrontDialogView' => 'view/dialog', 'AphrontErrorView' => 'view/form/error', 'AphrontException' => 'aphront/exception/base', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormMarkupControl' => 'view/form/control/markup', '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', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontNullView' => 'view/null', 'AphrontPageView' => 'view/page/base', 'AphrontPanelView' => 'view/layout/panel', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', 'AphrontQueryException' => 'storage/exception/base', 'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing', 'AphrontQueryParameterException' => 'storage/exception/parameter', 'AphrontQueryRecoverableException' => 'storage/exception/recoverable', 'AphrontRedirectException' => 'aphront/exception/redirect', 'AphrontRedirectResponse' => 'aphront/response/redirect', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', 'CelerityAPI' => 'infratructure/celerity/api', 'CelerityResourceController' => 'infratructure/celerity/controller', 'CelerityResourceMap' => 'infratructure/celerity/map', 'CelerityStaticResourceResponse' => 'infratructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', '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', + 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', - 'DifferentialFeedbackMail' => 'applications/differential/mail/feedback', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'Javelin' => 'infratructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controlller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', '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', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist', 'PhabricatorDirectoryController' => 'applications/directory/controller/base', 'PhabricatorDirectoryDAO' => 'applications/directory/storage/base', 'PhabricatorDirectoryItem' => 'applications/directory/storage/item', 'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete', 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLoginController' => 'applications/auth/controlller/login', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPHIDType' => 'applications/phid/storage/type', 'PhabricatorPHIDTypeEditController' => 'applications/phid/controller/typeedit', 'PhabricatorPHIDTypeListController' => 'applications/phid/controller/typelist', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', ), 'function' => array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infratructure/celerity/api', 'celerity_register_resource_map' => 'infratructure/celerity/map', 'javelin_render_tag' => 'infratructure/javelin/markup', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infratructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( + 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontNullView' => 'AphrontView', 'AphrontPageView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', + 'DifferentialCommentMail' => 'DifferentialMail', + 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', - 'DifferentialFeedbackMail' => 'DifferentialMail', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPHIDType' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDTypeEditController' => 'PhabricatorPHIDController', 'PhabricatorPHIDTypeListController' => 'PhabricatorPHIDController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index ee7c71caa..4344d37c7 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,178 +1,186 @@ <?php /* * Copyright 2011 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 getApplicationName() { return 'aphront-default'; } public function getURIMap() { return array( '/repository/' => array( '$' => 'RepositoryListController', 'new/$' => 'RepositoryEditController', 'edit/(?<id>\d+)/$' => 'RepositoryEditController', 'delete/(?<id>\d+)/$' => 'RepositoryDeleteController', ), '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?<id>\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?<id>\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?<id>\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?<id>\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?<view>info)/(?<phid>[^/]+)/' => 'PhabricatorFileViewController', '(?<view>view)/(?<phid>[^/]+)/' => 'PhabricatorFileViewController', '(?<view>download)/(?<phid>[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?<id>\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?<username>\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?<username>\w+)/$' => 'PhabricatorPeopleProfileController', '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?<method>[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?<method>[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?<id>\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?<filter>\w+)/$' => 'DifferentialRevisionListController', 'diff/(?<id>\d+)/$' => 'DifferentialDiffViewController', 'changeset/(?<id>\d+)/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?<id>\d+)/)?$' => 'DifferentialRevisionEditController', + 'comment/' => array( + 'preview/$' => 'DifferentialCommentPreviewController', + 'save/$' => 'DifferentialCommentSaveController', + 'inline/' => array( + 'preview/$' => 'DifferentialInlineCommentPreviewController', + 'edit/$' => 'DifferentialInlineCommentEditController', + ), + ), ), '/res/' => array( '(?<package>pkg/)?(?<hash>[a-f0-9]{8})/(?<path>.+\.(?:css|js))$' => 'CelerityResourceController', ), '/typeahead/' => array( 'common/(?<type>\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?<id>\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?<id>\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), '/login/' => 'PhabricatorLoginController', ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '<div class="aphront-unhandled-exception">'. '<h1>Unhandled Exception "'.$class.'": '.$message.'</h1>'. '<code>'.phutil_escape_html((string)$ex).'</code>'. '</div>'; $view = new PhabricatorStandardPageView(); $view->appendChild($content); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->appendChild( '<div style="padding: 2em 0;">'. $response->buildResponseString(). '</div>'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } } else if ($response instanceof Aphront404Response) { $failure = new AphrontRequestFailureView(); $failure->setHeader('404 Not Found'); $failure->appendChild( '<p>The page you requested was not found.</p>'); $view = new PhabricatorStandardPageView(); $view->setTitle('404 Not Found'); $view->appendChild($failure); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); $response->setHTTPResponseCode(404); return $response; } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } } diff --git a/src/aphront/response/400/Aphront400Response.php b/src/aphront/response/400/Aphront400Response.php new file mode 100644 index 000000000..46ecbe095 --- /dev/null +++ b/src/aphront/response/400/Aphront400Response.php @@ -0,0 +1,28 @@ +<?php + +/* + * Copyright 2011 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 Aphront400Response extends AphrontResponse { + + public function buildResponseString() { + return '400 Bad Request'; + } + +} diff --git a/src/aphront/response/400/__init__.php b/src/aphront/response/400/__init__.php new file mode 100644 index 000000000..8bc616ae0 --- /dev/null +++ b/src/aphront/response/400/__init__.php @@ -0,0 +1,12 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'aphront/response/base'); + + +phutil_require_source('Aphront400Response.php'); diff --git a/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php b/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php new file mode 100644 index 000000000..b534e4353 --- /dev/null +++ b/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php @@ -0,0 +1,56 @@ +<?php + +/* + * Copyright 2011 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 { + + 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->getStr('reviewers'); + + $editor = new DifferentialCommentEditor( + $revision, + $request->getUser()->getPHID(), + $action); + + $editor + ->setMessage($comment) + ->setAttachInlineComments(true) + ->setAddCC($action != DifferentialAction::ACTION_RESIGN) + ->setAddedReviewers($reviewers) + ->save(); + + // TODO: Diff change detection? + // TODO: Clear draft + + return id(new AphrontRedirectResponse()) + ->setURI('/D'.$revision->getID()); + } + +} diff --git a/src/applications/differential/mail/feedback/__init__.php b/src/applications/differential/controller/commentsave/__init__.php similarity index 50% copy from src/applications/differential/mail/feedback/__init__.php copy to src/applications/differential/controller/commentsave/__init__.php index dd323e630..d7c03ad62 100644 --- a/src/applications/differential/mail/feedback/__init__.php +++ b/src/applications/differential/controller/commentsave/__init__.php @@ -1,14 +1,18 @@ <?php /** * This file is automatically generated. Lint this module to rebuild it. * @generated */ -phutil_require_module('phabricator', 'applications/differential/constants/action'); -phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus'); -phutil_require_module('phabricator', 'applications/differential/mail/base'); +phutil_require_module('phabricator', 'aphront/response/400'); +phutil_require_module('phabricator', 'aphront/response/redirect'); +phutil_require_module('phabricator', 'applications/differential/controller/base'); +phutil_require_module('phabricator', 'applications/differential/editor/comment'); +phutil_require_module('phabricator', 'applications/differential/storage/revision'); +phutil_require_module('phutil', 'utils'); -phutil_require_source('DifferentialFeedbackMail.php'); + +phutil_require_source('DifferentialCommentSaveController.php'); diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php index ba28a982a..1e55f8c82 100644 --- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php @@ -1,1727 +1,1728 @@ <?php /* * Copyright 2011 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 { private $revisionID; public function willProcessRequest(array $data) { $this->revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $revision = id(new DifferentialRevision())->load($this->revisionID); if (!$revision) { return new Aphront404Response(); } $revision->loadRelationships(); $diffs = $revision->loadDiffs(); $target = end($diffs); $changesets = $target->loadChangesets(); $comments = $revision->loadComments(); $comments = array_merge( $this->getImplicitComments($revision), $comments); $object_phids = array_merge( $revision->getReviewers(), $revision->getCCPHIDs(), array( $revision->getAuthorPHID(), $request->getUser()->getPHID(), ), mpull($comments, 'getAuthorPHID')); $handles = id(new PhabricatorObjectHandleData($object_phids)) ->loadHandles(); $revision_detail = new DifferentialRevisionDetailView(); $revision_detail->setRevision($revision); $properties = $this->getRevisionProperties($revision, $target, $handles); $revision_detail->setProperties($properties); $actions = $this->getRevisionActions($revision); $revision_detail->setActions($actions); $comment_view = new DifferentialRevisionCommentListView(); $comment_view->setComments($comments); $comment_view->setHandles($handles); $diff_history = new DifferentialRevisionUpdateHistoryView(); $diff_history->setDiffs($diffs); $toc_view = new DifferentialDiffTableOfContentsView(); $toc_view->setChangesets($changesets); $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets($changesets); $comment_form = new DifferentialAddCommentView(); $comment_form->setRevision($revision); $comment_form->setActions($this->getRevisionCommentActions($revision)); + $comment_form->setActionURI('/differential/comment/save/'); return $this->buildStandardPageResponse( '<div class="differential-primary-pane">'. $revision_detail->render(). $comment_view->render(). $diff_history->render(). $toc_view->render(). $changeset_view->render(). $comment_form->render(). '</div>', array( 'title' => $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 getRevisionProperties( DifferentialRevision $revision, DifferentialDiff $diff, array $handles) { $properties = array(); $status = $revision->getStatus(); $status = DifferentialRevisionStatus::getNameForRevisionStatus($status); $properties['Revision Status'] = '<strong>'.$status.'</strong>'; $author = $handles[$revision->getAuthorPHID()]; $properties['Author'] = $author->renderLink(); $properties['Reviewers'] = $this->renderHandleLinkList( array_select_keys( $handles, $revision->getReviewers())); $properties['CCs'] = $this->renderHandleLinkList( array_select_keys( $handles, $revision->getCCPHIDs())); $path = $diff->getSourcePath(); if ($path) { $branch = $diff->getBranch() ? ' (' . $diff->getBranch() . ')' : ''; $host = $diff->getSourceMachine(); if ($host) { $host .= ':'; } $properties['Path'] = phutil_escape_html("{$host}{$path} {$branch}"); } $properties['Lint'] = 'TODO'; $properties['Unit'] = 'TODO'; return $properties; } 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()); $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_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', ); } else { $links[] = array( 'class' => 'subscribe-rem unavailable', 'name' => 'Automatically Subscribed', ); } $links[] = array( 'class' => 'transcripts-metamta', 'name' => 'MetaMTA Transcripts', 'href' => "/mail/?phid={$revision_phid}", ); return $links; } private function renderHandleLinkList(array $list) { if (empty($list)) { return '<em>None</em>'; } return implode(', ', mpull($list, 'renderLink')); } private function getRevisionCommentActions(DifferentialRevision $revision) { $actions = array( DifferentialAction::ACTION_COMMENT => true, ); $viewer_phid = $this->getRequest()->getUser()->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); if ($viewer_is_owner) { switch ($revision->getStatus()) { case DifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ABANDON] = true; break; case DifferentialRevisionStatus::NEEDS_REVISION: case DifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; break; case DifferentialRevisionStatus::COMMITTED: break; case DifferentialRevisionStatus::ABANDONED: $actions[DifferentialAction::ACTION_RECLAIM] = true; break; } } else { switch ($revision->getStatus()) { case DifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = true; break; case DifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ACCEPT] = true; break; case DifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_REJECT] = true; break; case DifferentialRevisionStatus::COMMITTED: case DifferentialRevisionStatus::ABANDONED: break; } } $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true; return array_keys($actions); } } /* protected function getRevisionActions(DifferentialRevision $revision) { $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $viewer_is_owner = ($viewer_id == $revision->getOwnerID()); $viewer_is_reviewer = ((array_search($viewer_id, $revision->getReviewers())) !== false); $viewer_is_cc = ((array_search($viewer_id, $revision->getCCFBIDs())) !== false); $status = $revision->getStatus(); $links = array(); if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $revision_id = $revision->getID(); $href = "/differential/subscribe/{$action}/{$revision_id}"; $links[] = array( $viewer_is_cc ? 'subscribe-disabled' : 'subscribe-enabled', <a href={$href}>{$viewer_is_cc ? 'Unsubscribe' : 'Subscribe'}</a>, ); } else { $links[] = array( 'subscribe-disabled unavailable', <a>Automatically Subscribed</a>, ); } $blast_uri = RedirectURI( '/intern/differential/?action=tasks&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'tasks', <a href={$blast_uri}>Edit Tasks</a>, ); $engineering_repository_id = RepositoryRef::getByCallsign('E')->getID(); $svn_revision = $revision->getSVNRevision(); if ($status == DifferentialConstants::COMMITTED && $svn_revision && $revision->getRepositoryID() == $engineering_repository_id) { $href = '/intern/push/request.php?rev='.$svn_revision; $href = RedirectURI($href)->setTier('intern'); $links[] = array( 'merge', <a href={$href} id="ask_for_merge_link">Ask for Merge</a>, ); } $links[] = array( 'herald-transcript', <a href={"/herald/transcript/?fbid=".$revision->getFBID()} >Herald Transcripts</a>, ); $links[] = array( 'metamta-transcript', <a href={"/mail/?view=all&fbid=".$revision->getFBID()} >MetaMTA Transcripts</a>, ); $list = <ul class="differential-actions" />; foreach ($links as $link) { list($class, $tag) = $link; $list->appendChild(<li class={$class}>{$tag}</li>); } return $list; /* // TODO // $sandcastle = $this->getSandcastleURI($diff); // if ($sandcastle) { // $fields['Sandcastle'] = <a href={$sandcastle}>{$sandcastle}</a>; // } $path = $diff->getSourcePath(); if ($path) { $host = $diff->getSourceMachine(); $branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : ''; if ($host) { // TODO // $user = $handles[$this->getRequest()->getViewerContext()->getUserID()] // ->getName(); $user = 'TODO'; $fields['Path'] = <x:frag> <a href={"ssh://{$user}@{$host}"}>{$host}</a>:{$path}{$branch} </x:frag>; } else { $fields['Path'] = $path; } } $reviewer_links = array(); foreach ($revision->getReviewers() as $reviewer) { $reviewer_links[] = <tools:handle handle={$handles[$reviewer]} link={true} />; } if ($reviewer_links) { $fields['Reviewers'] = array_implode(', ', $reviewer_links); } else { $fields['Reviewers'] = <em>None</em>; } $ccs = $revision->getCCFBIDs(); if ($ccs) { $links = array(); foreach ($ccs as $cc) { $links[] = <tools:handle handle={$handles[$cc]} link={true} />; } $fields['CCs'] = array_implode(', ', $links); } $blame_rev = $revision->getSvnBlameRevision(); if ($blame_rev) { if ($revision->getRepositoryRef() && is_numeric($blame_rev)) { $ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev); $fields['Blame Revision'] = <a href={URI($ref->getDetailURL())}> {$ref->getName()} </a>; } else { $fields['Blame Revision'] = $blame_rev; } } $tasks = $revision->getTaskHandles(); if ($tasks) { $links = array(); foreach ($tasks as $task) { $links[] = <tools:handle handle={$task} link={true} />; } $fields['Tasks'] = array_implode(<br />, $links); } $bugzilla_id = $revision->getBugzillaID(); if ($bugzilla_id) { $href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='. $bugzilla_id; $fields['Bugzilla'] = <a href={$href}>{'#'.$bugzilla_id}</a>; } $fields['Apply Patch'] = <tt>arc patch --revision {$revision->getID()}</tt>; if ($diff->getParentRevisionID()) { $parent = id(new DifferentialRevision())->load( $diff->getParentRevisionID()); if ($parent) { $fields['Depends On'] = <a href={$parent->getURI()}> D{$parent->getID()}: {$parent->getName()} </a>; } } $star = <span class="star">{"\xE2\x98\x85"}</span>; Javelin::initBehavior('differential-star-more'); switch ($diff->getLinted()) { case Diff::LINT_FAIL: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = <x:frag> <span class="star-warn">{$star} Lint Failures</span> {$more} </x:frag>; break; case Diff::LINT_WARNINGS: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = <x:frag> <span class="star-warn">{$star} Lint Warnings</span> {$more} </x:frag>; break; case Diff::LINT_OKAY: $fields['Lint'] = <span class="star-okay">{$star} Lint Free</span>; break; default: case Diff::LINT_NO: $fields['Lint'] = <span class="star-none">{$star} Not Linted</span>; break; } $unit_details = false; switch ($diff->getUnitTested()) { case Diff::UNIT_FAIL: $fields['Unit Tests'] = <span class="star-warn">{$star} Unit Test Failures</span>; $unit_details = true; break; case Diff::UNIT_WARN: $fields['Unit Tests'] = <span class="star-warn">{$star} Unit Test Warnings</span>; $unit_details = true; break; case Diff::UNIT_OKAY: $fields['Unit Tests'] = <span class="star-okay">{$star} Unit Tests Passed</span>; $unit_details = true; break; case Diff::UNIT_NO_TESTS: $fields['Unit Tests'] = <span class="star-none">{$star} No Test Coverage</span>; break; case Diff::UNIT_NO: default: $fields['Unit Tests'] = <span class="star-none">{$star} Not Unit Tested</span>; break; } if ($unit_details) { $fields['Unit Tests'] = <x:frag> {$fields['Unit Tests']} {$this->renderDiffPropertyMoreLink($diff, 'unit')} </x:frag>; } $platform_impact = $revision->getPlatformImpact(); if ($platform_impact) { $fields['Platform Impact'] = <text linebreaks="true">{$platform_impact}</text>; } return $fields; } } /* protected function getSandcastleURI(Diff $diff) { $uri = $this->getDiffProperty($diff, 'facebook:sandcastle_uri'); if (!$uri) { $uri = $diff->getSandboxURL(); } return $uri; } protected function getDiffProperty(Diff $diff, $property, $default = null) { $diff_id = $diff->getID(); if (empty($this->diffProperties[$diff_id])) { $props = id(new DifferentialDiffProperty()) ->loadAllWhere('diffID = %s', $diff_id); $dict = array_pull($props, 'getData', 'getName'); $this->diffProperties[$diff_id] = $dict; } return idx($this->diffProperties[$diff_id], $property, $default); } public function process() { $uri = $this->getRequest()->getPath(); if (starts_with($uri, '/d')) { return <alite:redirect uri={strtoupper($uri)}/>; } $revision = id(new DifferentialRevision())->load($this->revisionID); if (!$revision) { throw new Exception("Bad revision ID."); } $diffs = id(new Diff())->loadAllWhere( 'revisionID = %d', $revision->getID()); $diffs = array_psort($diffs, 'getID'); $request = $this->getRequest(); $new = $request->getInt('new'); $old = $request->getInt('old'); if (($new || $old) && $new <= $old) { throw new Exception( "You can only view the diff of an older update relative to a newer ". "update."); } if ($new && empty($diffs[$new])) { throw new Exception( "The 'new' diff does not exist."); } else if ($new) { $diff = $diffs[$new]; } else { $diff = end($diffs); if (!$diff) { throw new Exception("No diff attached to this revision?"); } $new = $diff->getID(); } $target_diff = $diff; if ($old && empty($diffs[$old])) { throw new Exception( "The 'old' diff does not exist."); } $rows = array(array('Base', '', true, false, null, $diff->getSourceControlBaseRevision() ? $diff->getSourceControlBaseRevision() : <em>Master</em>)); $idx = 0; foreach ($diffs as $cdiff) { $rows[] = array( 'Diff '.(++$idx), $cdiff->getID(), $cdiff->getID() != max(array_pull($diffs, 'getID')), true, $cdiff->getDateCreated(), $cdiff->getDescription() ? $cdiff->getDescription() : <em>No description available.</em>, $cdiff->getUnitTested(), $cdiff->getLinted()); } $diff_table = <table class="differential-diff-differ"> <tr> <th>Diff</th> <th>Diff ID</th> <th>Description</th> <th>Age</th> <th>Lint</th> <th>Unit</th> </tr> </table>; $ii = 0; $old_ids = array(); foreach ($rows as $row) { $xold = null; if ($row[2]) { $lradio = <input name="old" value={$row[1]} type="radio" disabled={$row[1] >= $new} checked={$old == $row[1]} />; if ($old == $row[1]) { $xold = 'old-now'; } $old_ids[] = $lradio->requireUniqueID(); } else { $lradio = null; } $xnew = null; if ($row[3]) { $rradio = <input name="new" value={$row[1]} type="radio" sigil="new-radio" checked={$new == $row[1]} />; if ($new == $row[1]) { $xnew = 'new-now'; } } else { $rradio = null; } if ($row[3]) { $unit_star = 'star-none'; switch ($row[6]) { case Diff::UNIT_FAIL: case Diff::UNIT_WARN: $unit_star = 'star-warn'; break; case Diff::UNIT_OKAY: $unit_star = 'star-okay'; break; } $lint_star = 'star-none'; switch ($row[7]) { case Diff::LINT_FAIL: case Diff::LINT_WARNINGS: $lint_star = 'star-warn'; break; case Diff::LINT_OKAY: $lint_star = 'star-okay'; break; } $star = "\xE2\x98\x85"; $unit_star = <span class={$unit_star}> <span class="star">{$star}</span> </span>; $lint_star = <span class={$lint_star}> <span class="star">{$star}</span> </span>; } else { $unit_star = null; $lint_star = null; } $diff_table->appendChild( <tr class={++$ii % 2 ? 'alt' : null}> <td class="name">{$row[0]}</td> <td class="diffid">{$row[1]}</td> <td class="desc">{$row[5]}</td> <td class="age">{$row[4] ? ago(time() - $row[4]) : null}</td> <td class="star">{$lint_star}</td> <td class="star">{$unit_star}</td> <td class={"old {$xold}"}>{$lradio}</td> <td class={"new {$xnew}"}>{$rradio}</td> </tr>); } Javelin::initBehavior('differential-diff-radios', array( 'radios' => $old_ids, )); $diff_table->appendChild( <tr> <td colspan="8" class="diff-differ-submit"> <label>Whitespace Changes:</label> {id(<select name="whitespace" />)->setOptions( array( 'ignore-all' => 'Ignore All', 'ignore-trailing' => 'Ignore Trailing', 'show-all' => 'Show All', ), $request->getStr('whitespace'))}{' '} <button type="submit">Show Diff</button> </td> </tr>); $diff_table = <div class="differential-table-of-contents"> <h1>Revision Update History</h1> <form action={URI::getRequestURI()} method="get"> {$diff_table} </form> </div>; $load_ids = array_filter(array($old, $diff->getID())); $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $raw_objects = queryfx_all( smc_get_db('cdb.differential', 'r'), 'SELECT * FROM changeset WHERE changeset.diffID IN (%Ld)', $load_ids); $raw_objects = array_group($raw_objects, 'diffID'); $objects = $raw_objects[$diff->getID()]; if (!$objects) { $changesets = array(); } else { $changesets = id(new DifferentialChangeset())->loadAllFromArray($objects); } $against_warn = null; $against_map = array(); $visible_changesets = array(); if ($old) { $old_diff = $diffs[$old]; $new_diff = $diff; $old_path = $old_diff->getSourcePath(); $new_path = $new_diff->getSourcePath(); $old_prefix = null; $new_prefix = null; if ((strlen($old_path) < strlen($new_path)) && (!strncmp($old_path, $new_path, strlen($old_path)))) { $old_prefix = substr($new_path, strlen($old_path)); } if ((strlen($new_path) < strlen($old_path)) && (!strncmp($old_path, $new_path, strlen($new_path)))) { $new_prefix = substr($old_path, strlen($new_path)); } $old_changesets = id(new DifferentialChangeset()) ->loadAllFromArray($raw_objects[$old]); $old_changesets = array_pull($old_changesets, null, 'getFilename'); if ($new_prefix) { $rekeyed_map = array(); foreach ($old_changesets as $key => $value) { $rekeyed_map[$new_prefix.$key] = $value; } $old_changesets = $rekeyed_map; } foreach ($changesets as $key => $changeset) { $file = $old_prefix.$changeset->getFilename(); if (isset($old_changesets[$file])) { $checksum = $changeset->getChecksum(); if ($checksum !== null && $checksum == $old_changesets[$file]->getChecksum()) { unset($changesets[$key]); unset($old_changesets[$file]); } else { $against_map[$changeset->getID()] = $old_changesets[$file]->getID(); unset($old_changesets[$file]); } } } foreach ($old_changesets as $changeset) { $changesets[$changeset->getID()] = $changeset; $against_map[$changeset->getID()] = -1; } $against_warn = <tools:notice title="NOTE - Diff of Diffs"> You are viewing a synthetic diff between two previous diffs in this revision. You can not add new inline comments (for now). </tools:notice>; } else { $visible_changesets = array_pull($changesets, 'getID'); } $changesets = array_psort($changesets, 'getSortKey'); $all_changesets = $changesets; $warning = null; $limit = 100; if (count($changesets) > $limit && !$this->getRequest()->getStr('large')) { $count = number_format(count($changesets)); $warning = <tools:notice title="Very Large Diff"> This diff is extremely large and affects {$count} files. Only the first {number_format($limit)} files are shown. <strong> <a href={$revision->getURI().'?large=true'}>Show All Files</a> </strong> </tools:notice>; $changesets = array_slice($changesets, 0, $limit); if (!$old) { $visible_changesets = array_pull($changesets, 'getID'); } } $detail_view = <differential:changeset-detail-view changesets={$changesets} revision={$revision} against={$against_map} edit={empty($against_map)} whitespace={$request->getStr('whitespace')} />; $table_of_contents = <differential:changeset-table-of-contents changesets={$all_changesets} />; $implied_feedback = array(); foreach (array( 'summarize' => $revision->getSummary(), 'testplan' => $revision->getTestPlan(), 'annotate' => $revision->getNotes(), ) as $type => $text) { if (!strlen($text)) { continue; } $implied_feedback[] = id(new DifferentialFeedback()) ->setUserID($revision->getOwnerID()) ->setAction($type) ->setDateCreated($revision->getDateCreated()) ->setContent($text); } $feedback = id(new DifferentialFeedback())->loadAllWithRevision($revision); $feedback = array_merge($implied_feedback, $feedback); $inline_comments = $this->loadInlineComments($feedback, $changesets); $diff_map = array(); $diffs = array_psort($diffs, 'getID'); foreach ($diffs as $diff) { $diff_map[$diff->getID()] = count($diff_map) + 1; } $visible_changesets = array_fill_keys($visible_changesets, true); $hidden_changesets = array(); foreach ($changesets as $changeset) { $id = $changeset->getID(); if (isset($visible_changesets[$id])) { continue; } $hidden_changesets[$id] = $diff_map[$changeset->getDiffID()]; } $revision->loadRelationships(); $ccs = $revision->getCCFBIDs(); $reviewers = $revision->getReviewers(); $actors = array_pull($feedback, 'getUserID'); $actors[] = $revision->getOwnerID(); $tasks = array(); assoc_get_by_type( $revision->getFBID(), 22284182462, // TODO: include issue, DIFFCAMP_TASK_ASSOC $start = null, $limit = null, $pending = true, $tasks); memcache_dispatch(); $tasks = array_keys($tasks); $preparer = new Preparer(); $fbids = array_merge_fast( array($actors, array($viewer_id), $reviewers, $ccs, $tasks), true); $handles = array(); $handle_data = id(new ToolsHandleData($fbids, $handles)) ->needNames() ->needAlternateNames() ->needAlternateIDs() ->needThumbnails(); $preparer->waitFor($handle_data); $preparer->go(); $revision->attachTaskHandles(array_select_keys($handles, $tasks)); $inline_comments = array_group($inline_comments, 'getFeedbackID'); $engine = new RemarkupEngine(); $engine->enableFeature(RemarkupEngine::FEATURE_GUESS_IMAGES); $engine->enableFeature(RemarkupEngine::FEATURE_YOUTUBE); $engine->setCurrentSandcastle($this->getSandcastleURI($target_diff)); $feed = array(); foreach ($feedback as $comment) { $inlines = null; if (isset($inline_comments[$comment->getID()])) { $inlines = $inline_comments[$comment->getID()]; } $feed[] = <differential:feedback feedback={$comment} handle={$handles[$comment->getUserID()]} engine={$engine} inline={$inlines} changesets={$changesets} hidden={$hidden_changesets} />; } $feed = $this->renderFeedbackList($feed, $feedback, $viewer_id); $fields = $this->getDetailFields($revision, $diff, $handles); $table = <table class="differential-revision-properties" />; foreach ($fields as $key => $value) { $table->appendChild( <tr> <th>{$key}:</th><td>{$value}</td> </tr>); } $quick_links = $this->getQuickLinks($revision); $edit_link = null; if ($revision->getOwnerID() == $viewer_id) { $edit_link = '/differential/revision/edit/'.$revision->getID().'/'; $edit_link = <x:frag> {' '}(<a href={$edit_link}>Edit Revision</a>) </x:frag>; } $info = <div class="differential-revision-information"> <div class="differential-revision-actions"> {$quick_links} </div> <div class="differential-revision-detail"> <h1>{$revision->getName()}{$edit_link}</h1> {$table} </div> </div>; $actions = $this->getRevisionActions($revision); $revision_id = $revision->getID(); Javelin::initBehavior( 'differential-feedback-preview', array( 'uri' => '/differential/preview/'.$revision->getFBID().'/', 'preview' => 'overall-feedback-preview', 'action' => 'feedback-action', 'content' => 'feedback-content', )); Javelin::initBehavior( 'differential-inline-comment-preview', array( 'uri' => '/differential/inline-preview/'.$revision_id.'/'.$new.'/', 'preview' => 'inline-comment-preview', )); $content = SavedCopy::loadData( $viewer_id, SavedCopy::Type_DifferentialRevisionFeedback, $revision->getFBID()); $inline_comment_container = <div id="inline-comment-preview"><p>Loading...</p></div>; $feedback = id(new DifferentialFeedback()) ->setAction('none') ->setUserID($viewer_id) ->setContent($content); $preview = <div class="differential-feedback differential-feedback-preview"> <div id="overall-feedback-preview"> <differential:feedback feedback={$feedback} engine={$engine} preview={true} handle={$handles[$viewer_id]} /> </div> {$inline_comment_container} </div>; $syntax_link = <a href={'http://www.intern.facebook.com/intern/wiki/index.php' . '/Articles/Remarkup_Syntax_Reference'} target="_blank" tabindex="4">Remarkup Reference</a>; Javelin::initBehavior( 'differential-add-reviewers', array( 'src' => redirect_str('/datasource/employee/', 'tools'), 'tokenizer' => 'reviewer-tokenizer', 'select' => 'feedback-action', 'row' => 'reviewer-tokenizer-row', )); $feedback_form = <x:frag> <div class="differential-feedback-form"> <tools:form method="post" action={"/differential/revision/feedback/{$revision_id}/"}> <h1>Provide Feedback</h1> <tools:fieldset> <tools:control type="select" label="Action"> {id(<select name="action" id="feedback-action" tabindex="1" />) ->setOptions($actions)} </tools:control> <tools:control type="text" label="Reviewers" style="display: none;" id="reviewer-tokenizer-row"> <javelin:tokenizer-template id="reviewer-tokenizer" name="reviewers" /> </tools:control> <tools:control type="textarea" label="Feedback" caption={$syntax_link}> <tools:droppable-textarea id="feedback-content" name="feedback" tabindex="2"> {$content} </tools:droppable-textarea> </tools:control> <tools:control type="submit"> <button type="submit" tabindex="3">Clowncopterize</button> </tools:control> </tools:fieldset> </tools:form> </div> {$preview} </x:frag>; $notice = null; if ($this->getRequest()->getBool('diff_changed')) { $notice = <tools:notice title="Revision Updated Recently"> This revision was updated with a <strong>new diff</strong> while you were providing feedback. Your inline comments appear on the <strong>old diff</strong>. </tools:notice>; } return <differential:standard-page title={$revision->getName()}> <div class="differential-primary-pane"> {$warning} {$notice} {$info} <div class="differential-feedback"> {$feed} </div> {$diff_table} {$table_of_contents} {$against_warn} {$detail_view} {$feedback_form} </div> </differential:standard-page>; } protected function getQuickLinks(DifferentialRevision $revision) { $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $viewer_is_owner = ($viewer_id == $revision->getOwnerID()); $viewer_is_reviewer = ((array_search($viewer_id, $revision->getReviewers())) !== false); $viewer_is_cc = ((array_search($viewer_id, $revision->getCCFBIDs())) !== false); $status = $revision->getStatus(); $links = array(); if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $revision_id = $revision->getID(); $href = "/differential/subscribe/{$action}/{$revision_id}"; $links[] = array( $viewer_is_cc ? 'subscribe-disabled' : 'subscribe-enabled', <a href={$href}>{$viewer_is_cc ? 'Unsubscribe' : 'Subscribe'}</a>, ); } else { $links[] = array( 'subscribe-disabled unavailable', <a>Automatically Subscribed</a>, ); } $blast_uri = RedirectURI( '/intern/differential/?action=blast&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'blast', <a href={$blast_uri}>Blast Revision</a>, ); $blast_uri = RedirectURI( '/intern/differential/?action=tasks&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'tasks', <a href={$blast_uri}>Edit Tasks</a>, ); if ($viewer_is_owner && false) { $perflab_uri = RedirectURI( '/intern/differential/?action=perflab&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'perflab', <a href={$perflab_uri}>Run in Perflab</a>, ); } $engineering_repository_id = RepositoryRef::getByCallsign('E')->getID(); $svn_revision = $revision->getSVNRevision(); if ($status == DifferentialConstants::COMMITTED && $svn_revision && $revision->getRepositoryID() == $engineering_repository_id) { $href = '/intern/push/request.php?rev='.$svn_revision; $href = RedirectURI($href)->setTier('intern'); $links[] = array( 'merge', <a href={$href} id="ask_for_merge_link">Ask for Merge</a>, ); } $links[] = array( 'herald-transcript', <a href={"/herald/transcript/?fbid=".$revision->getFBID()} >Herald Transcripts</a>, ); $links[] = array( 'metamta-transcript', <a href={"/mail/?view=all&fbid=".$revision->getFBID()} >MetaMTA Transcripts</a>, ); $list = <ul class="differential-actions" />; foreach ($links as $link) { list($class, $tag) = $link; $list->appendChild(<li class={$class}>{$tag}</li>); } return $list; } protected function getDetailFields( DifferentialRevision $revision, Diff $diff, array $handles) { $fields = array(); $fields['Revision Status'] = $this->getRevisionStatusDisplay($revision); $author = $revision->getOwnerID(); $fields['Author'] = <tools:handle handle={$handles[$author]} link={true} />; $sandcastle = $this->getSandcastleURI($diff); if ($sandcastle) { $fields['Sandcastle'] = <a href={$sandcastle}>{$sandcastle}</a>; } $path = $diff->getSourcePath(); if ($path) { $host = $diff->getSourceMachine(); $branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : ''; if ($host) { $user = $handles[$this->getRequest()->getViewerContext()->getUserID()] ->getName(); $fields['Path'] = <x:frag> <a href={"ssh://{$user}@{$host}"}>{$host}</a>:{$path}{$branch} </x:frag>; } else { $fields['Path'] = $path; } } $reviewer_links = array(); foreach ($revision->getReviewers() as $reviewer) { $reviewer_links[] = <tools:handle handle={$handles[$reviewer]} link={true} />; } if ($reviewer_links) { $fields['Reviewers'] = array_implode(', ', $reviewer_links); } else { $fields['Reviewers'] = <em>None</em>; } $ccs = $revision->getCCFBIDs(); if ($ccs) { $links = array(); foreach ($ccs as $cc) { $links[] = <tools:handle handle={$handles[$cc]} link={true} />; } $fields['CCs'] = array_implode(', ', $links); } $blame_rev = $revision->getSvnBlameRevision(); if ($blame_rev) { if ($revision->getRepositoryRef() && is_numeric($blame_rev)) { $ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev); $fields['Blame Revision'] = <a href={URI($ref->getDetailURL())}> {$ref->getName()} </a>; } else { $fields['Blame Revision'] = $blame_rev; } } $tasks = $revision->getTaskHandles(); if ($tasks) { $links = array(); foreach ($tasks as $task) { $links[] = <tools:handle handle={$task} link={true} />; } $fields['Tasks'] = array_implode(<br />, $links); } $bugzilla_id = $revision->getBugzillaID(); if ($bugzilla_id) { $href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='. $bugzilla_id; $fields['Bugzilla'] = <a href={$href}>{'#'.$bugzilla_id}</a>; } $fields['Apply Patch'] = <tt>arc patch --revision {$revision->getID()}</tt>; if ($diff->getParentRevisionID()) { $parent = id(new DifferentialRevision())->load( $diff->getParentRevisionID()); if ($parent) { $fields['Depends On'] = <a href={$parent->getURI()}> D{$parent->getID()}: {$parent->getName()} </a>; } } $star = <span class="star">{"\xE2\x98\x85"}</span>; Javelin::initBehavior('differential-star-more'); switch ($diff->getLinted()) { case Diff::LINT_FAIL: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = <x:frag> <span class="star-warn">{$star} Lint Failures</span> {$more} </x:frag>; break; case Diff::LINT_WARNINGS: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = <x:frag> <span class="star-warn">{$star} Lint Warnings</span> {$more} </x:frag>; break; case Diff::LINT_OKAY: $fields['Lint'] = <span class="star-okay">{$star} Lint Free</span>; break; default: case Diff::LINT_NO: $fields['Lint'] = <span class="star-none">{$star} Not Linted</span>; break; } $unit_details = false; switch ($diff->getUnitTested()) { case Diff::UNIT_FAIL: $fields['Unit Tests'] = <span class="star-warn">{$star} Unit Test Failures</span>; $unit_details = true; break; case Diff::UNIT_WARN: $fields['Unit Tests'] = <span class="star-warn">{$star} Unit Test Warnings</span>; $unit_details = true; break; case Diff::UNIT_OKAY: $fields['Unit Tests'] = <span class="star-okay">{$star} Unit Tests Passed</span>; $unit_details = true; break; case Diff::UNIT_NO_TESTS: $fields['Unit Tests'] = <span class="star-none">{$star} No Test Coverage</span>; break; case Diff::UNIT_NO: default: $fields['Unit Tests'] = <span class="star-none">{$star} Not Unit Tested</span>; break; } if ($unit_details) { $fields['Unit Tests'] = <x:frag> {$fields['Unit Tests']} {$this->renderDiffPropertyMoreLink($diff, 'unit')} </x:frag>; } $platform_impact = $revision->getPlatformImpact(); if ($platform_impact) { $fields['Platform Impact'] = <text linebreaks="true">{$platform_impact}</text>; } return $fields; } protected function renderDiffPropertyMoreLink(Diff $diff, $name) { $target = <div class="star-more" style="display: none;"> <div class="star-loading">Loading...</div> </div>; $meta = array( 'target' => $target->requireUniqueID(), 'uri' => '/differential/diffprop/'.$diff->getID().'/'.$name.'/', ); $more = <span sigil="star-link-container"> · <a mustcapture="true" sigil="star-more" href="#" meta={$meta}>Show Details</a> </span>; return <x:frag>{$more}{$target}</x:frag>; } protected function loadInlineComments(array $feedback, array &$changesets) { $inline_comments = array(); $feedback_ids = array_filter(array_pull($feedback, 'getID')); if (!$feedback_ids) { return $inline_comments; } $inline_comments = id(new DifferentialInlineComment()) ->loadAllWhere('feedbackID in (%Ld)', $feedback_ids); $load_changesets = array(); $load_hunks = 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()) ->loadAllWithIDs($changeset_ids); } if ($more_changesets) { $changesets += $more_changesets; $changesets = array_psort($changesets, 'getSortKey'); } return $inline_comments; } protected function getRevisionStatusDisplay(DifferentialRevision $revision) { $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $viewer_is_owner = ($viewer_id == $revision->getOwnerID()); $status = $revision->getStatus(); $more = null; switch ($status) { case DifferentialConstants::NEEDS_REVIEW: $message = 'Pending Review'; break; case DifferentialConstants::NEEDS_REVISION: $message = 'Awaiting Revision'; if ($viewer_is_owner) { $more = 'Make the requested changes and update the revision.'; } break; case DifferentialConstants::ACCEPTED: $message = 'Ready for Commit'; if ($viewer_is_owner) { $more = <x:frag> Run <tt>arc commit</tt> (svn) or <tt>arc amend</tt> (git) to proceed. </x:frag>; } break; case DifferentialConstants::COMMITTED: $message = 'Committed'; $ref = $revision->getRevisionRef(); $more = $ref ? (<a href={URI($ref->getDetailURL())}> {$ref->getName()} </a>) : null; $engineering_repository_id = RepositoryRef::getByCallsign('E')->getID(); if ($revision->getSVNRevision() && $revision->getRepositoryID() == $engineering_repository_id) { Javelin::initBehavior( 'differential-revtracker-status', array( 'uri' => '/differential/revtracker/'.$revision->getID().'/', 'statusId' => 'revtracker_status', 'mergeLinkId' => 'ask_for_merge_link', )); } break; case DifferentialConstants::ABANDONED: $message = 'Abandoned'; break; default: throw new Exception("Unknown revision status."); } if ($more) { $message = <x:frag> <strong id="revtracker_status">{$message}</strong> · {$more} </x:frag>; } else { $message = <strong id="revtracker_status">{$message}</strong>; } return $message; } protected function renderFeedbackList(array $xhp, array $obj, $viewer_id) { // Use magical heuristics to try to hide older comments. $obj = array_reverse($obj); $obj = array_values($obj); $xhp = array_reverse($xhp); $xhp = array_values($xhp); $last_comment = null; foreach ($obj as $position => $feedback) { if ($feedback->getUserID() == $viewer_id) { if ($last_comment === null) { $last_comment = $position; } else if ($last_comment == $position - 1) { // If you made consecuitive comments, show them all. This is a spaz // rule for epriestley comments. $last_comment = $position; } } } $header = array(); $hide = array(); if ($last_comment !== null) { foreach ($obj as $position => $feedback) { $action = $feedback->getAction(); if ($action == 'testplan' || $action == 'summarize') { // Always show summary and test plan. $header[] = $xhp[$position]; unset($xhp[$position]); continue; } if ($position <= $last_comment) { // Always show comments after your last comment. continue; } if ($position < 3) { // Always show the most recent 3 comments. continue; } // Hide everything else. $hide[] = $position; } } if (count($hide) <= 3) { // Don't hide if there's not much to hide. $hide = array(); } $header = array_reverse($header); $hidden = array_select_keys($xhp, $hide); $visible = array_diff_key($xhp, $hidden); $visible = array_reverse($visible); $hidden = array_reverse($hidden); if ($hidden) { Javelin::initBehavior( 'differential-show-all-feedback', array( 'markup' => id(<x:frag>{$hidden}</x:frag>)->toString(), )); $hidden = <div sigil="all-feedback-container"> <div class="older-replies-are-hidden"> {number_format(count($hidden))} older replies are hidden. <a href="#" sigil="show-all-feedback" mustcapture="true">Show all feedback.</a> </div> </div>; } else { $hidden = null; } return <x:frag> {$header} {$hidden} {$visible} </x:frag>; } } protected function getDetailFields( DifferentialRevision $revision, Diff $diff, array $handles) { $fields = array(); $fields['Revision Status'] = $this->getRevisionStatusDisplay($revision); $author = $revision->getOwnerID(); $fields['Author'] = <tools:handle handle={$handles[$author]} link={true} />; $sandcastle = $this->getSandcastleURI($diff); if ($sandcastle) { $fields['Sandcastle'] = <a href={$sandcastle}>{$sandcastle}</a>; } $path = $diff->getSourcePath(); if ($path) { $host = $diff->getSourceMachine(); $branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : ''; if ($host) { $user = $handles[$this->getRequest()->getViewerContext()->getUserID()] ->getName(); $fields['Path'] = <x:frag> <a href={"ssh://{$user}@{$host}"}>{$host}</a>:{$path}{$branch} </x:frag>; } else { $fields['Path'] = $path; } } $reviewer_links = array(); foreach ($revision->getReviewers() as $reviewer) { $reviewer_links[] = <tools:handle handle={$handles[$reviewer]} link={true} />; } if ($reviewer_links) { $fields['Reviewers'] = array_implode(', ', $reviewer_links); } else { $fields['Reviewers'] = <em>None</em>; } $ccs = $revision->getCCFBIDs(); if ($ccs) { $links = array(); foreach ($ccs as $cc) { $links[] = <tools:handle handle={$handles[$cc]} link={true} />; } $fields['CCs'] = array_implode(', ', $links); } $blame_rev = $revision->getSvnBlameRevision(); if ($blame_rev) { if ($revision->getRepositoryRef() && is_numeric($blame_rev)) { $ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev); $fields['Blame Revision'] = <a href={URI($ref->getDetailURL())}> {$ref->getName()} </a>; } else { $fields['Blame Revision'] = $blame_rev; } } $tasks = $revision->getTaskHandles(); if ($tasks) { $links = array(); foreach ($tasks as $task) { $links[] = <tools:handle handle={$task} link={true} />; } $fields['Tasks'] = array_implode(<br />, $links); } $bugzilla_id = $revision->getBugzillaID(); if ($bugzilla_id) { $href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='. $bugzilla_id; $fields['Bugzilla'] = <a href={$href}>{'#'.$bugzilla_id}</a>; } $fields['Apply Patch'] = <tt>arc patch --revision {$revision->getID()}</tt>; if ($diff->getParentRevisionID()) { $parent = id(new DifferentialRevision())->load( $diff->getParentRevisionID()); if ($parent) { $fields['Depends On'] = <a href={$parent->getURI()}> D{$parent->getID()}: {$parent->getName()} </a>; } } $star = <span class="star">{"\xE2\x98\x85"}</span>; Javelin::initBehavior('differential-star-more'); switch ($diff->getLinted()) { case Diff::LINT_FAIL: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = <x:frag> <span class="star-warn">{$star} Lint Failures</span> {$more} </x:frag>; break; case Diff::LINT_WARNINGS: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = <x:frag> <span class="star-warn">{$star} Lint Warnings</span> {$more} </x:frag>; break; case Diff::LINT_OKAY: $fields['Lint'] = <span class="star-okay">{$star} Lint Free</span>; break; default: case Diff::LINT_NO: $fields['Lint'] = <span class="star-none">{$star} Not Linted</span>; break; } $unit_details = false; switch ($diff->getUnitTested()) { case Diff::UNIT_FAIL: $fields['Unit Tests'] = <span class="star-warn">{$star} Unit Test Failures</span>; $unit_details = true; break; case Diff::UNIT_WARN: $fields['Unit Tests'] = <span class="star-warn">{$star} Unit Test Warnings</span>; $unit_details = true; break; case Diff::UNIT_OKAY: $fields['Unit Tests'] = <span class="star-okay">{$star} Unit Tests Passed</span>; $unit_details = true; break; case Diff::UNIT_NO_TESTS: $fields['Unit Tests'] = <span class="star-none">{$star} No Test Coverage</span>; break; case Diff::UNIT_NO: default: $fields['Unit Tests'] = <span class="star-none">{$star} Not Unit Tested</span>; break; } if ($unit_details) { $fields['Unit Tests'] = <x:frag> {$fields['Unit Tests']} {$this->renderDiffPropertyMoreLink($diff, 'unit')} </x:frag>; } $platform_impact = $revision->getPlatformImpact(); if ($platform_impact) { $fields['Platform Impact'] = <text linebreaks="true">{$platform_impact}</text>; } return $fields; } */ diff --git a/src/applications/differential/editor/comment/DifferentialCommentEditor.php b/src/applications/differential/editor/comment/DifferentialCommentEditor.php new file mode 100755 index 000000000..b9e23528a --- /dev/null +++ b/src/applications/differential/editor/comment/DifferentialCommentEditor.php @@ -0,0 +1,329 @@ +<?php + +/* + * Copyright 2011 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 DifferentialCommentEditor { + + protected $revision; + protected $actorPHID; + protected $action; + + protected $attachInlineComments; + protected $message; + protected $addCC; + protected $changedByCommit; + protected $addedReviewers = array(); + + public function __construct( + DifferentialRevision $revision, + $actor_phid, + $action) { + + $this->revision = $revision; + $this->actorPHID = $actor_phid; + $this->action = $action; + } + + public function setMessage($message) { + $this->message = $message; + return $this; + } + + public function setAttachInlineComments($attach) { + $this->attachInlineComments = $attach; + return $this; + } + + public function setAddCC($add) { + $this->addCC = $add; + return $this; + } + + public function setChangedByCommit($changed_by_commit) { + $this->changedByCommit = $changed_by_commit; + return $this; + } + + public function getChangedByCommit() { + return $this->changedByCommit; + } + + public function setAddedReviewers($added_reviewers) { + $this->addedReviewers = $added_reviewers; + return $this; + } + + public function getAddedReviewers() { + return $this->addedReviewers; + } + + public function save() { + $revision = $this->revision; + $action = $this->action; + $actor_phid = $this->actorPHID; + $actor_is_author = ($actor_phid == $revision->getAuthorPHID()); + $revision_status = $revision->getStatus(); + + $revision->loadRelationships(); + $reviewer_phids = $revision->getReviewers(); + if ($reviewer_phids) { + $reviewer_phids = array_combine($reviewer_phids, $reviewer_phids); + } + + switch ($action) { + case DifferentialAction::ACTION_COMMENT: + break; + + case DifferentialAction::ACTION_RESIGN: + if ($actor_is_author) { + throw new Exception('You can not resign from your own revision!'); + } + if (isset($reviewer_phids[$actor_phid])) { + DifferentialRevisionEditor::alterReviewers( + $revision, + $reviewer_phids, + $rem = array($actor_phid), + $add = array(), + $actor_phid); + } + break; + + case DifferentialAction::ACTION_ABANDON: + if (!$actor_is_author) { + throw new Exception('You can only abandon your revisions.'); + } + if ($revision_status == DifferentialRevisionStatus::COMMITTED) { + throw new Exception('You can not abandon a committed revision.'); + } + if ($revision_status == DifferentialRevisionStatus::ABANDONED) { + $action = DifferentialAction::ACTION_COMMENT; + break; + } + + $revision + ->setStatus(DifferentialRevisionStatus::ABANDONED) + ->save(); + break; + + case DifferentialAction::ACTION_ACCEPT: + if ($actor_is_author) { + throw new Exception('You can not accept your own revision.'); + } + if (($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW) && + ($revision_status != DifferentialRevisionStatus::NEEDS_REVISION)) { + $action = DifferentialAction::ACTION_COMMENT; + break; + } + + $revision + ->setStatus(DifferentialRevisionStatus::ACCEPTED) + ->save(); + + if (!isset($reviewer_phids[$actor_phid])) { + DifferentialRevisionEditor::addReviewers( + $revision, + $reviewer_phids, + $rem = array(), + $add = array($actor_phid), + $actor_phid); + } + break; + + case DifferentialAction::ACTION_REQUEST: + if (!$actor_is_author) { + throw new Exception('You must own a revision to request review.'); + } + if (($revision_status != DifferentialRevisionStatus::NEEDS_REVISION) && + ($revision_status != DifferentialRevisionStatus::ACCEPTED)) { + $action = DifferentialAction::ACTION_COMMENT; + break; + } + + $revision + ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW) + ->save(); + break; + + case DifferentialAction::ACTION_REJECT: + if ($actor_is_author) { + throw new Exception( + 'You can not request changes to your own revision.'); + } + if (($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW) && + ($revision_status != DifferentialRevisionStatus::ACCEPTED)) { + $action = DifferentialAction::ACTION_COMMENT; + break; + } + + if (!isset($reviewer_phids[$actor_phid])) { + DifferentialRevisionEditor::addReviewers( + $revision, + $reviewer_phids, + $rem = array(), + $add = array($actor_phid), + $actor_phid); + } + + $revision + ->setStatus(DifferentialRevisionStatus::NEEDS_REVISION) + ->save(); + break; + + case DifferentialAction::ACTION_RECLAIM: + if (!$actor_is_author) { + throw new Exception('You can not reclaim a revision you do not own.'); + } + if ($revision_status != DifferentialRevisionStatus::ABANDONED) { + $action = DifferentialAction::ACTION_COMMENT; + break; + } + $revision + ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW) + ->save(); + break; + + case DifferentialAction::ACTION_COMMIT: + // This is handled externally. (TODO) + break; + + case DifferentialAction::ACTION_ADDREVIEWERS: + $added_reviewers = $this->getAddedReviewers(); + foreach ($added_reviewers as $k => $user_phid) { + if ($user_phid == $revision->getAuthorPHID()) { + unset($added_reviewers[$k]); + } + if (!empty($reviewer_phids[$user_phid])) { + unset($added_reviewers[$k]); + } + } + + $added_reviewers = array_unique($added_reviewers); + + if ($added_reviewers) { + DifferentialRevisionEditor::addReviewers( + $revision, + $reviewer_phids, + $rem = array(), + $add = $added_reviewers, + $actor_phid); + +// TODO +// $unixnames = unixname_multi($added_reviewers); + $usernames = $added_reviewers; + + $this->message = + 'Added reviewers: '.implode(', ', $usernames)."\n\n". + $this->message; + + } else { + $action = DifferentialAction::ACTION_COMMENT; + } + break; + + default: + throw new Exception('Unsupported action.'); + } + + // Reload relationships to pick up any reviewer changes. + $revision->loadRelationships(); + +/* + TODO + + $inline_comments = array(); + if ($this->attachInlineComments) { + $inline_comments = id(new DifferentialInlineComment()) + ->loadAllUnsaved($revision, $this->actorPHID); + } +*/ + + $comment = id(new DifferentialComment()) + ->setAuthorPHID($this->actorPHID) + ->setRevisionID($revision->getID()) + ->setAction($action) + ->setContent((string)$this->message) + ->save(); + +/* + $diff = id(new Diff())->loadActiveWithRevision($revision); + $changesets = id(new DifferentialChangeset())->loadAllWithDiff($diff); + + if ($inline_comments) { + // We may have feedback on non-current changesets. Rather than orphaning + // it, just submit it. This is non-ideal but not horrible. + $inline_changeset_ids = array_pull($inline_comments, 'getChangesetID'); + $load = array(); + foreach ($inline_changeset_ids as $id) { + if (empty($changesets[$id])) { + $load[] = $id; + } + } + if ($load) { + $changesets += id(new DifferentialChangeset())->loadAllWithIDs($load); + } + foreach ($inline_comments as $inline) { + $inline->setFeedbackID($feedback->getID()); + $inline->save(); + } + } +*/ + + id(new DifferentialCommentMail( + $revision, + $this->actorPHID, + $comment, + /* $changesets TODO */ array(), + /* $inline_comments TODO */ array())) + ->setToPHIDs( + array_merge( + $revision->getReviewers(), + array($revision->getAuthorPHID()))) + ->setCCPHIDs($revision->getCCPHIDs()) + ->setChangedByCommit($this->getChangedByCommit()) + ->send(); + +/* + + tODO + + if ($this->addCC) { + require_module_lazy('site/tools/differential/lib/editor/revision'); + DifferentialRevisionEditor::addCCFBID( + $revision, + $this->actorPHID, + $this->actorPHID); + } +*/ + +/* + + TODO + + $event = array( + 'revision_id' => $revision->getID(), + 'fbid' => $revision->getFBID(), + 'feedback_id' => $feedback->getID(), + 'action' => $feedback->getAction(), + 'actor' => $this->actorPHID, + ); + id(new ToolsTimelineEvent('difx', fb_json_encode($event)))->record(); +*/ + + return $comment; + } + +} diff --git a/src/applications/differential/mail/feedback/__init__.php b/src/applications/differential/editor/comment/__init__.php similarity index 55% copy from src/applications/differential/mail/feedback/__init__.php copy to src/applications/differential/editor/comment/__init__.php index dd323e630..5eb988d3a 100644 --- a/src/applications/differential/mail/feedback/__init__.php +++ b/src/applications/differential/editor/comment/__init__.php @@ -1,14 +1,18 @@ <?php /** * This file is automatically generated. Lint this module to rebuild it. * @generated */ phutil_require_module('phabricator', 'applications/differential/constants/action'); phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus'); -phutil_require_module('phabricator', 'applications/differential/mail/base'); +phutil_require_module('phabricator', 'applications/differential/editor/revision'); +phutil_require_module('phabricator', 'applications/differential/mail/comment'); +phutil_require_module('phabricator', 'applications/differential/storage/comment'); +phutil_require_module('phutil', 'utils'); -phutil_require_source('DifferentialFeedbackMail.php'); + +phutil_require_source('DifferentialCommentEditor.php'); diff --git a/src/applications/differential/mail/base/DifferentialMail.php b/src/applications/differential/mail/base/DifferentialMail.php index 6b30df6fb..322049845 100755 --- a/src/applications/differential/mail/base/DifferentialMail.php +++ b/src/applications/differential/mail/base/DifferentialMail.php @@ -1,311 +1,311 @@ <?php /* * Copyright 2011 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. */ abstract class DifferentialMail { const SUBJECT_PREFIX = '[Differential]'; protected $to = array(); protected $cc = array(); protected $actorName; protected $actorID; protected $revision; - protected $feedback; + protected $comment; protected $changesets; protected $inlineComments; protected $isFirstMailAboutRevision; protected $isFirstMailToRecipients; protected $heraldTranscriptURI; protected $heraldRulesHeader; public function getActorName() { return $this->actorName; } public function setActorName($actor_name) { $this->actorName = $actor_name; return $this; } abstract protected function renderSubject(); abstract protected function renderBody(); public function setXHeraldRulesHeader($header) { $this->heraldRulesHeader = $header; return $this; } public function send() { $to_phids = $this->getToPHIDs(); if (!$to_phids) { throw new Exception('No "To:" users provided!'); } $message_id = $this->getMessageID(); $cc_phids = $this->getCCPHIDs(); $subject = $this->buildSubject(); $body = $this->buildBody(); $mail = new PhabricatorMetaMTAMail(); if ($this->getActorID()) { $mail->setFrom($this->getActorID()); $mail->setReplyTo($this->getReplyHandlerEmailAddress()); } else { $mail->setFrom($this->getReplyHandlerEmailAddress()); } $mail ->addTos($to_phids) ->addCCs($cc_phids) ->setSubject($subject) ->setBody($body) ->setIsHTML($this->shouldMarkMailAsHTML()) ->addHeader('Thread-Topic', $this->getRevision()->getTitle()) ->addHeader('Thread-Index', $this->generateThreadIndex()); if ($this->isFirstMailAboutRevision()) { $mail->addHeader('Message-ID', $message_id); } else { $mail->addHeader('In-Reply-To', $message_id); $mail->addHeader('References', $message_id); } if ($this->heraldRulesHeader) { $mail->addHeader('X-Herald-Rules', $this->heraldRulesHeader); } $mail->setRelatedPHID($this->getRevision()->getPHID()); // Save this to the MetaMTA queue for later delivery to the MTA. $mail->save(); } protected function buildSubject() { return self::SUBJECT_PREFIX.' '.$this->renderSubject(); } protected function shouldMarkMailAsHTML() { return false; } protected function buildBody() { $actions = array(); $body = $this->renderBody(); $body .= <<<EOTEXT ACTIONS Reply to comment, or !accept, !reject, !abandon, !resign, or !showdiff. EOTEXT; if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) { $xscript_uri = $this->getHeraldTranscriptURI(); $body .= <<<EOTEXT MANAGE HERALD RULES http://todo.com/herald/ WHY DID I GET THIS EMAIL? {$xscript_uri} Tip: use the X-Herald-Rules header to filter Herald messages in your client. EOTEXT; } return $body; } protected function getReplyHandlerEmailAddress() { // TODO $phid = $this->getRevision()->getPHID(); $server = 'todo.example.com'; return "differential+{$phid}@{$server}"; } protected function formatText($text) { $text = explode("\n", $text); foreach ($text as &$line) { $line = rtrim(' '.$line); } unset($line); return implode("\n", $text); } public function setToPHIDs(array $to) { $this->to = $this->filterContactPHIDs($to); return $this; } public function setCCPHIDs(array $cc) { $this->cc = $this->filterContactPHIDs($cc); return $this; } protected function filterContactPHIDs(array $phids) { return $phids; // TODO: actually do this? // Differential revisions use Subscriptions for CCs, so any arbitrary // PHID can end up CC'd to them. Only try to actually send email PHIDs // which have ToolsHandle types that are marked emailable. If we don't // filter here, sending the email will fail. /* $handles = array(); prep(new ToolsHandleData($phids, $handles)); foreach ($handles as $phid => $handle) { if (!$handle->isEmailable()) { unset($handles[$phid]); } } return array_keys($handles); */ } protected function getToPHIDs() { return $this->to; } protected function getCCPHIDs() { return $this->cc; } public function setActorID($actor_id) { $this->actorID = $actor_id; return $this; } public function getActorID() { return $this->actorID; } public function setRevision($revision) { $this->revision = $revision; return $this; } public function getRevision() { return $this->revision; } protected function getMessageID() { $phid = $this->getRevision()->getPHID(); // TODO return "<differential-rev-{$phid}-req@TODO.com>"; } - public function setFeedback($feedback) { - $this->feedback = $feedback; + public function setComment($comment) { + $this->comment = $comment; return $this; } - public function getFeedback() { - return $this->feedback; + public function getComment() { + return $this->comment; } public function setChangesets($changesets) { $this->changesets = $changesets; return $this; } public function getChangesets() { return $this->changesets; } public function setInlineComments(array $inline_comments) { $this->inlineComments = $inline_comments; return $this; } public function getInlineComments() { return $this->inlineComments; } public function renderRevisionDetailLink() { $uri = $this->getRevisionURI(); return "REVISION DETAIL\n {$uri}"; } public function getRevisionURI() { // TODO return 'http://local.aphront.com/D'.$this->getRevision()->getID(); } public function setIsFirstMailToRecipients($first) { $this->isFirstMailToRecipients = $first; return $this; } public function isFirstMailToRecipients() { return $this->isFirstMailToRecipients; } public function setIsFirstMailAboutRevision($first) { $this->isFirstMailAboutRevision = $first; return $this; } public function isFirstMailAboutRevision() { return $this->isFirstMailAboutRevision; } protected function generateThreadIndex() { // When threading, Outlook ignores the 'References' and 'In-Reply-To' // headers that most clients use. Instead, it uses a custom 'Thread-Index' // header. The format of this header is something like this (from // camel-exchange-folder.c in Evolution Exchange): /* A new post to a folder gets a 27-byte-long thread index. (The value * is apparently unique but meaningless.) Each reply to a post gets a * 32-byte-long thread index whose first 27 bytes are the same as the * parent's thread index. Each reply to any of those gets a * 37-byte-long thread index, etc. The Thread-Index header contains a * base64 representation of this value. */ // The specific implementation uses a 27-byte header for the first email // a recipient receives, and a random 5-byte suffix (32 bytes total) // thereafter. This means that all the replies are (incorrectly) siblings, // but it would be very difficult to keep track of the entire tree and this // gets us reasonable client behavior. $base = substr(md5($this->getRevision()->getPHID()), 0, 27); if (!$this->isFirstMailAboutRevision()) { // not totally sure, but it seems like outlook orders replies by // thread-index rather than timestamp, so to get these to show up in the // right order we use the time as the last 4 bytes. $base .= ' ' . pack("N", time()); } return base64_encode($base); } public function setHeraldTranscriptURI($herald_transcript_uri) { $this->heraldTranscriptURI = $herald_transcript_uri; return $this; } public function getHeraldTranscriptURI() { return $this->heraldTranscriptURI; } } diff --git a/src/applications/differential/mail/feedback/DifferentialFeedbackMail.php b/src/applications/differential/mail/comment/DifferentialCommentMail.php similarity index 86% rename from src/applications/differential/mail/feedback/DifferentialFeedbackMail.php rename to src/applications/differential/mail/comment/DifferentialCommentMail.php index c3715126f..11ae980ab 100755 --- a/src/applications/differential/mail/feedback/DifferentialFeedbackMail.php +++ b/src/applications/differential/mail/comment/DifferentialCommentMail.php @@ -1,114 +1,114 @@ <?php /* * Copyright 2011 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 DifferentialFeedbackMail extends DifferentialMail { +class DifferentialCommentMail extends DifferentialMail { protected $changedByCommit; public function setChangedByCommit($changed_by_commit) { $this->changedByCommit = $changed_by_commit; return $this; } public function getChangedByCommit() { return $this->changedByCommit; } public function __construct( DifferentialRevision $revision, $actor_id, - DifferentialFeedback $feedback, + DifferentialComment $comment, array $changesets, array $inline_comments) { $this->setRevision($revision); $this->setActorID($actor_id); - $this->setFeedback($feedback); + $this->setComment($comment); $this->setChangesets($changesets); $this->setInlineComments($inline_comments); } protected function renderSubject() { $revision = $this->getRevision(); $verb = $this->getVerb(); - return ucwords($verb).': '.$revision->getName(); + return ucwords($verb).': '.$revision->getTitle(); } protected function getVerb() { - $feedback = $this->getFeedback(); - $action = $feedback->getAction(); - $verb = DifferentialAction::getActionVerb($action); + $comment = $this->getComment(); + $action = $comment->getAction(); + $verb = DifferentialAction::getActionPastTenseVerb($action); return $verb; } protected function renderBody() { - $feedback = $this->getFeedback(); + $comment = $this->getComment(); $actor = $this->getActorName(); - $name = $this->getRevision()->getName(); + $name = $this->getRevision()->getTitle(); $verb = $this->getVerb(); $body = array(); $body[] = "{$actor} has {$verb} the revision \"{$name}\"."; $body[] = null; - $content = $feedback->getContent(); + $content = $comment->getContent(); if (strlen($content)) { $body[] = $this->formatText($content); $body[] = null; } if ($this->getChangedByCommit()) { $body[] = 'CHANGED PRIOR TO COMMIT'; $body[] = ' This revision was updated prior to commit.'; $body[] = null; } $inlines = $this->getInlineComments(); if ($inlines) { $body[] = 'INLINE COMMENTS'; $changesets = $this->getChangesets(); foreach ($inlines as $inline) { $changeset = $changesets[$inline->getChangesetID()]; if (!$changeset) { throw new Exception('Changeset missing!'); } $file = $changeset->getFilename(); $line = $inline->renderLineRange(); $content = $inline->getContent(); $body[] = $this->formatText("{$file}:{$line} {$content}"); } $body[] = null; } $body[] = $this->renderRevisionDetailLink(); $revision = $this->getRevision(); if ($revision->getStatus() == DifferentialRevisionStatus::COMMITTED) { $rev_ref = $revision->getRevisionRef(); if ($rev_ref) { $body[] = " Detail URL: ".$rev_ref->getDetailURL(); } } $body[] = null; return implode("\n", $body); } } diff --git a/src/applications/differential/mail/feedback/__init__.php b/src/applications/differential/mail/comment/__init__.php similarity index 86% rename from src/applications/differential/mail/feedback/__init__.php rename to src/applications/differential/mail/comment/__init__.php index dd323e630..38f77fa0c 100644 --- a/src/applications/differential/mail/feedback/__init__.php +++ b/src/applications/differential/mail/comment/__init__.php @@ -1,14 +1,14 @@ <?php /** * This file is automatically generated. Lint this module to rebuild it. * @generated */ phutil_require_module('phabricator', 'applications/differential/constants/action'); phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus'); phutil_require_module('phabricator', 'applications/differential/mail/base'); -phutil_require_source('DifferentialFeedbackMail.php'); +phutil_require_source('DifferentialCommentMail.php'); diff --git a/src/applications/differential/view/addcomment/DifferentialAddCommentView.php b/src/applications/differential/view/addcomment/DifferentialAddCommentView.php index ec836065b..7d8c135ba 100644 --- a/src/applications/differential/view/addcomment/DifferentialAddCommentView.php +++ b/src/applications/differential/view/addcomment/DifferentialAddCommentView.php @@ -1,66 +1,71 @@ <?php /* * Copyright 2011 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. */ final class DifferentialAddCommentView extends AphrontView { private $revision; private $actions; private $actionURI; public function setRevision($revision) { $this->revision = $revision; return $this; } public function setActions(array $actions) { $this->actions = $actions; return $this; } public function setActionURI($uri) { $this->actionURI = $uri; } public function render() { + $revision = $this->revision; + $actions = array(); foreach ($this->actions as $action) { $actions[$action] = DifferentialAction::getActionVerb($action); } $form = new AphrontFormView(); $form ->setAction($this->actionURI) + ->addHiddenInput('revision_id', $revision->getID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Action') + ->setName('action') ->setOptions($actions)) ->appendChild( id(new AphrontFormTextAreaControl()) + ->setName('comment') ->setLabel('Comment')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Comment')); return '<div class="differential-panel">'. '<h1>Add Comment</h1>'. $form->render(). '</div>'; } }