Page MenuHomePhorge

No OneTemporary

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 4831225f97..d67bb525ce 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 ee7c71caa6..4344d37c7e 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 0000000000..46ecbe095a
--- /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 0000000000..8bc616ae03
--- /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 0000000000..b534e43539
--- /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 dd323e6309..d7c03ad621 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 ba28a982a5..1e55f8c825 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">
&middot;
<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>
&middot; {$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 0000000000..b9e23528ad
--- /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 dd323e6309..5eb988d3ae 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 6b30df6fb8..322049845e 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 c3715126fd..11ae980ab2 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 dd323e6309..38f77fa0cb 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 ec836065bc..7d8c135baf 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>';
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:20 (2 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127378
Default Alt Text
(112 KB)

Event Timeline