diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7647a8ef85..9d9939698c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,284 +1,296 @@ <?php /** * This file is automatically generated. Use 'phutil_mapper.php' to rebuild it. * @generated */ phutil_register_library_map(array( 'class' => array( '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', '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', + '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', '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', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', + 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'Javelin' => 'infratructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', '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', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( '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', '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', + 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => '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', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', '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/applications/conduit/controller/api/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php index e3bd8e0e5a..9cd95681c4 100644 --- a/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php @@ -1,185 +1,189 @@ <?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 PhabricatorConduitAPIController extends PhabricatorConduitController { + public function shouldRequireLogin() { + return false; + } + private $method; public function willProcessRequest(array $data) { $this->method = $data['method']; return $this; } public function processRequest() { $time_start = microtime(true); $request = $this->getRequest(); $method = $this->method; $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method); $api_request = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $metadata = array(); try { if (!class_exists($method_class)) { throw new Exception( "Unable to load the implementation class for method '{$method}'. ". "You may have misspelled the method, need to define ". "'{$method_class}', or need to run 'arc build'."); } // Fake out checkModule, the class has already been autoloaded by the // class_exists() call above. $method_handler = newv($method_class, array()); if (isset($_REQUEST['params']) && is_array($_REQUEST['params'])) { $params_post = $request->getArr('params'); foreach ($params_post as $key => $value) { $params_post[$key] = json_decode($value, true); } $params = $params_post; } else { $params_json = $request->getStr('params'); if (!strlen($params_json)) { $params = array(); } else { $params = json_decode($params_json, true); if (!is_array($params)) { throw new Exception( "Invalid parameter information was passed to method ". "'{$method}', could not decode JSON serialization."); } } } $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); $api_request = new ConduitAPIRequest($params); try { $result = $method_handler->executeMethod($api_request); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); $error_info = $method_handler->getErrorDescription($error_code); } } catch (Exception $ex) { $result = null; $error_code = 'ERR-CONDUIT-CORE'; $error_info = $ex->getMessage(); } $time_end = microtime(true); $connection_id = null; if (idx($metadata, 'connectionID')) { $connection_id = $metadata['connectionID']; } else if (($method == 'conduit.connect') && $result) { $connection_id = idx($result, 'connectionID'); } $log->setConnectionID($connection_id); $log->setError((string)$error_code); $log->setDuration(1000000 * ($time_end - $time_start)); $log->save(); $result = array( 'result' => $result, 'error_code' => $error_code, 'error_info' => $error_info, ); switch ($request->getStr('output')) { case 'human': return $this->buildHumanReadableResponse( $method, $api_request, $result); case 'json': default: return id(new AphrontFileResponse()) ->setMimeType('application/json') ->setContent(json_encode($result)); } } private function buildHumanReadableResponse( $method, ConduitAPIRequest $request = null, $result = null) { $param_rows = array(); $param_rows[] = array('Method', phutil_escape_html($method)); if ($request) { foreach ($request->getAllParameters() as $key => $value) { $param_rows[] = array( phutil_escape_html($key), phutil_escape_html(json_encode($value)), ); } } $param_table = new AphrontTableView($param_rows); $param_table->setColumnClasses( array( 'header', 'wide', )); $result_rows = array(); foreach ($result as $key => $value) { $result_rows[] = array( phutil_escape_html($key), phutil_escape_html(json_encode($value)), ); } $result_table = new AphrontTableView($result_rows); $result_table->setColumnClasses( array( 'header', 'wide', )); $param_panel = new AphrontPanelView(); $param_panel->setHeader('Method Parameters'); $param_panel->appendChild($param_table); $result_panel = new AphrontPanelView(); $result_panel->setHeader('Method Result'); $result_panel->appendChild($result_table); return $this->buildStandardPageResponse( array( $param_panel, $result_panel, ), array( 'title' => 'Method Call Result', )); } } diff --git a/src/applications/differential/controller/diffview/DifferentialDiffViewController.php b/src/applications/differential/controller/diffview/DifferentialDiffViewController.php index 67829ec10b..2e686d1a16 100644 --- a/src/applications/differential/controller/diffview/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/diffview/DifferentialDiffViewController.php @@ -1,84 +1,86 @@ <?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 DifferentialDiffViewController extends DifferentialController { private $id; public function willProcessRequest(array $data) { $this->id = $data['id']; } public function processRequest() { $diff = id(new DifferentialDiff())->load($this->id); if (!$diff) { return new Aphront404Response(); } $action_panel = new AphrontPanelView(); $action_panel->setHeader('Preview Diff'); $action_panel->setWidth(AphrontPanelView::WIDTH_WIDE); $action_panel->appendChild( '<p class="aphront-panel-instructions">Review the diff for correctness. '. 'When you are satisfied, either <strong>create a new revision</strong> '. 'or <strong>update an existing revision</strong>.'); $action_form = new AphrontFormView(); $action_form ->setAction('/differential/revision/edit/') + ->addHiddenInput('diffID', $diff->getID()) + ->addHiddenInput('viaDiffView', 1) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Attach To') ->setName('revisionID') ->setValue('') ->setOptions(array( '' => "Create a new Revision...", ))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Continue')); $action_panel->appendChild($action_form); $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); $table_of_contents = id(new DifferentialDiffTableOfContentsView()) ->setChangesets($changesets); $details = id(new DifferentialChangesetListView()) ->setChangesets($changesets); return $this->buildStandardPageResponse( '<div class="differential-primary-pane">'. implode( "\n", array( $action_panel->render(), $table_of_contents->render(), $details->render(), )). '</div>', array( 'title' => 'Diff View', )); } } diff --git a/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php b/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php index 1f9a1679dc..4e38ac654e 100644 --- a/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php +++ b/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php @@ -1,145 +1,192 @@ <?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 DifferentialRevisionEditController extends DifferentialController { private $id; public function willProcessRequest(array $data) { $this->id = idx($data, 'id'); } public function processRequest() { if ($this->id) { $revision = id(new DifferentialRevision())->load($this->id); if (!$revision) { return new Aphront404Response(); } } else { $revision = new DifferentialRevision(); } -/* - $e_name = true; - $errors = array(); $request = $this->getRequest(); - if ($request->isFormPost()) { - $category->setName($request->getStr('name')); - $category->setSequence($request->getStr('sequence')); + $diff_id = $request->getInt('diffID'); + if ($diff_id) { + $diff = id(new DifferentialDiff())->load($diff_id); + if (!$diff) { + return new Aphront404Response(); + } + if ($diff->getRevisionID()) { + // TODO: Redirect? + throw new Exception("This diff is already attached to a revision!"); + } + } else { + $diff = null; + } + + $e_title = true; + $e_testplan = true; + $errors = array(); - if (!strlen($category->getName())) { - $errors[] = 'Category name is required.'; - $e_name = 'Required'; + if ($request->isFormPost() && !$request->getStr('viaDiffView')) { + $revision->setTitle($request->getStr('title')); + $revision->setSummary($request->getStr('summary')); + $revision->setTestPlan($request->getStr('testplan')); + $revision->setBlameRevision($request->getStr('blame')); + $revision->setRevertPlan($request->getStr('revert')); + + if (!strlen(trim($revision->getTitle()))) { + $errors[] = 'You must provide a title.'; + $e_title = 'Required'; + } + + if (!strlen(trim($revision->getTestPlan()))) { + $errors[] = 'You must provide a test plan.'; + $e_testplan = 'Required'; + } + + $user_phid = $request->getUser()->getPHID(); + + if (in_array($user_phid, $request->getArr('reviewers'))) { + $errors[] = 'You may not review your own revision.'; } if (!$errors) { - $category->save(); - return id(new AphrontRedirectResponse()) - ->setURI('/directory/category/'); + $editor = new DifferentialRevisionEditor($revision, $user_phid); + if ($diff) { + $editor->addDiff($diff, $request->getStr('comments')); + } + $editor->setCCPHIDs($request->getArr('cc')); + $editor->setReviewers($request->getArr('reviewers')); + $editor->save(); + + $response = id(new AphrontRedirectResponse()) + ->setURI('/D'.$revision->getID()); } - } - $error_view = null; - if ($errors) { - $error_view = id(new AphrontErrorView()) - ->setTitle('Form Errors') - ->setErrors($errors); + $reviewer_phids = $request->getArr('reviewers'); + $cc_phids = $request->getArr('cc'); + } else { +// $reviewer_phids = $revision->getReviewers(); +// $cc_phids = $revision->getCCPHIDs(); + $reviewer_phids = array(); + $cc_phids = array(); } -*/ - $e_name = true; - $e_testplan = true; $form = new AphrontFormView(); + if ($diff) { + $form->addHiddenInput('diffID', $diff->getID()); + } + if ($revision->getID()) { $form->setAction('/differential/revision/edit/'.$revision->getID().'/'); } else { $form->setAction('/differential/revision/edit/'); } - $reviewer_map = array( - 1 => 'A Zebra', - 2 => 'Pie Messenger', - ); + $error_view = null; + if ($errors) { + $error_view = id(new AphrontErrorView()) + ->setTitle('Form Errors') + ->setErrors($errors); + } $form ->appendChild( id(new AphrontFormTextAreaControl()) - ->setLabel('Name') - ->setName('name') - ->setValue($revision->getName()) - ->setError($e_name)) + ->setLabel('Title') + ->setName('title') + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + ->setValue($revision->getTitle()) + ->setError($e_title)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Summary') ->setName('summary') ->setValue($revision->getSummary())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Test Plan') ->setName('testplan') ->setValue($revision->getTestPlan()) ->setError($e_testplan)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Reviewers') ->setName('reviewers') - ->setDatasource('/typeahead/common/user/') + ->setDatasource('/typeahead/common/users/') ->setValue($reviewer_map)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('CC') ->setName('cc') - ->setDatasource('/typeahead/common/user/') + ->setDatasource('/typeahead/common/mailable/') ->setValue($reviewer_map)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Blame Revision') ->setName('blame') ->setValue($revision->getBlameRevision()) ->setCaption('Revision which broke the stuff which this '. 'change fixes.')) ->appendChild( id(new AphrontFormTextAreaControl()) - ->setLabel('Revert') + ->setLabel('Revert Plan') ->setName('revert') ->setValue($revision->getRevertPlan()) - ->setCaption('Special steps required to safely revert this change.')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue('Save')); + ->setCaption('Special steps required to safely revert this change.')); + + $submit = id(new AphrontFormSubmitControl()) + ->setValue('Save'); + if ($diff) { + $submit->addCancelButton('/differential/diff/'.$diff->getID().'/'); + } else { + $submit->addCancelButton('/D'.$revision->getID()); + } + + $form->appendChild($submit); $panel = new AphrontPanelView(); if ($revision->getID()) { $panel->setHeader('Edit Differential Revision'); } else { $panel->setHeader('Create New Differential Revision'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); - $error_view = null; return $this->buildStandardPageResponse( array($error_view, $panel), array( 'title' => 'Edit Differential Revision', )); } } diff --git a/src/applications/differential/controller/revisionedit/__init__.php b/src/applications/differential/controller/revisionedit/__init__.php index 4c5fa1f85e..872135cb60 100644 --- a/src/applications/differential/controller/revisionedit/__init__.php +++ b/src/applications/differential/controller/revisionedit/__init__.php @@ -1,19 +1,24 @@ <?php /** * This file is automatically generated. Lint this module to rebuild it. * @generated */ phutil_require_module('phabricator', 'aphront/response/404'); +phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/differential/controller/base'); +phutil_require_module('phabricator', 'applications/differential/editor/revision'); +phutil_require_module('phabricator', 'applications/differential/storage/diff'); phutil_require_module('phabricator', 'applications/differential/storage/revision'); phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/submit'); +phutil_require_module('phabricator', 'view/form/control/textarea'); +phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phutil', 'utils'); phutil_require_source('DifferentialRevisionEditController.php'); diff --git a/src/applications/differential/editor/revision/DifferentialRevisionEditor.php b/src/applications/differential/editor/revision/DifferentialRevisionEditor.php new file mode 100644 index 0000000000..3f9e91d010 --- /dev/null +++ b/src/applications/differential/editor/revision/DifferentialRevisionEditor.php @@ -0,0 +1,550 @@ +<?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. + */ + +/** + * Handle major edit operations to DifferentialRevision -- adding and removing + * reviewers, diffs, and CCs. Unlike simple edits, these changes trigger + * complicated email workflows. + */ +class DifferentialRevisionEditor { + + protected $revision; + protected $actorPHID; + + protected $cc = null; + protected $reviewers = null; + protected $diff; + protected $comments; + protected $silentUpdate; + + public function __construct(DifferentialRevision $revision, $actor_phid) { + $this->revision = $revision; + $this->actorPHID = $actor_phid; + } + +/* + public static function newRevisionFromRawMessageWithDiff( + DifferentialRawMessage $message, + Diff $diff, + $user) { + + if ($message->getRevisionID()) { + throw new Exception( + "The provided commit message is already associated with a ". + "Differential revision."); + } + + if ($message->getReviewedByNames()) { + throw new Exception( + "The provided commit message contains a 'Reviewed By:' field."); + } + + $revision = new DifferentialRevision(); + $revision->setPHID($revision->generatePHID()); + + $revision->setOwnerID($user); + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); + $revision->attachReviewers(array()); + $revision->attachCCPHIDs(array()); + + $editor = new DifferentialRevisionEditor($revision, $user); + + self::copyFields($editor, $revision, $message, $user); + + $editor->addDiff($diff, null); + $editor->save(); + + return $revision; + } + + public static function newRevisionFromConduitWithDiff( + array $fields, + Diff $diff, + $user) { + + $revision = new DifferentialRevision(); + $revision->setPHID($revision->generatePHID()); + + $revision->setOwnerID($user); + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); + $revision->attachReviewers(array()); + $revision->attachCCPHIDs(array()); + + $editor = new DifferentialRevisionEditor($revision, $user); + + $editor->copyFieldFromConduit($fields); + + $editor->addDiff($diff, null); + $editor->save(); + + return $revision; + } + + + public static function copyFields( + DifferentialRevisionEditor $editor, + DifferentialRevision $revision, + DifferentialRawMessage $message, + $user) { + + $revision->setName($message->getTitle()); + $revision->setSummary($message->getSummary()); + $revision->setTestPlan($message->getTestPlan()); + $revision->setSVNBlameRevision($message->getBlameRevision()); + $revision->setRevert($message->getRevertPlan()); + $revision->setPlatformImpact($message->getPlatformImpact()); + $revision->setBugzillaID($message->getBugzillaID()); + + $editor->setReviewers($message->getReviewerPHIDs()); + $editor->setCCPHIDs($message->getCCPHIDs()); + } + + public function copyFieldFromConduit(array $fields) { + + $user = $this->actorPHID; + $revision = $this->revision; + + $revision->setName($fields['title']); + $revision->setSummary($fields['summary']); + $revision->setTestPlan($fields['testPlan']); + $revision->setSVNBlameRevision($fields['blameRevision']); + $revision->setRevert($fields['revertPlan']); + $revision->setPlatformImpact($fields['platformImpact']); + $revision->setBugzillaID($fields['bugzillaID']); + + $this->setReviewers($fields['reviewerGUIDs']); + $this->setCCPHIDs($fields['ccGUIDs']); + } +*/ + + public function getRevision() { + return $this->revision; + } + + public function setReviewers(array $reviewers) { + $this->reviewers = $reviewers; + return $this; + } + + public function setCCPHIDs(array $cc) { + $this->cc = $cc; + return $this; + } + + public function addDiff(DifferentialDiff $diff, $comments) { + if ($diff->getRevisionID() && + $diff->getRevisionID() != $this->getRevision()->getID()) { + $diff_id = (int)$diff->getID(); + $targ_id = (int)$this->getRevision()->getID(); + $real_id = (int)$diff->getRevisionID(); + throw new Exception( + "Can not attach diff #{$diff_id} to Revision D{$targ_id}, it is ". + "already attached to D{$real_id}."); + } + $this->diff = $diff; + $this->comments = $comments; + return $this; + } + + protected function getDiff() { + return $this->diff; + } + + protected function getComments() { + return $this->comments; + } + + protected function getActorPHID() { + return $this->actorPHID; + } + + public function isNewRevision() { + return !$this->getRevision()->getID(); + } + + /** + * A silent update does not trigger Herald rules or send emails. This is used + * for auto-amends at commit time. + */ + public function setSilentUpdate($silent) { + $this->silentUpdate = $silent; + return $this; + } + + public function save() { + $revision = $this->getRevision(); + +// TODO +// $revision->openTransaction(); + + $is_new = $this->isNewRevision(); + if ($is_new) { + // These fields aren't nullable; set them to sensible defaults if they + // haven't been configured. We're just doing this so we can generate an + // ID for the revision if we don't have one already. + $revision->setLineCount(0); + if ($revision->getStatus() === null) { + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); + } + if ($revision->getTitle() === null) { + $revision->setTitle('Untitled Revision'); + } + if ($revision->getOwnerPHID() === null) { + $revision->setOwnerPHID($this->getActorPHID()); + } + + $revision->save(); + } + + $revision->loadRelationships(); + + if ($this->reviewers === null) { + $this->reviewers = $revision->getReviewers(); + } + + if ($this->cc === null) { + $this->cc = $revision->getCCPHIDs(); + } + + // We're going to build up three dictionaries: $add, $rem, and $stable. The + // $add dictionary has added reviewers/CCs. The $rem dictionary has + // reviewers/CCs who have been removed, and the $stable array is + // reviewers/CCs who haven't changed. We're going to send new reviewers/CCs + // a different ("welcome") email than we send stable reviewers/CCs. + + $old = array( + 'rev' => array_fill_keys($revision->getReviewers(), true), + 'ccs' => array_fill_keys($revision->getCCPHIDs(), true), + ); + + $diff = $this->getDiff(); + + $xscript_header = null; + $xscript_uri = null; + + $new = array( + 'rev' => array_fill_keys($this->reviewers, true), + 'ccs' => array_fill_keys($this->cc, true), + ); + + + $rem_ccs = array(); + if ($diff) { + $diff->setRevisionID($revision->getID()); + $revision->setLineCount($diff->getLineCount()); + +// TODO! +// $revision->setRepositoryID($diff->getRepositoryID()); + +/* + $iface = new DifferentialRevisionHeraldable($revision); + $iface->setExplicitCCs($new['ccs']); + $iface->setExplicitReviewers($new['rev']); + $iface->setForbiddenCCs($revision->getForbiddenCCPHIDs()); + $iface->setForbiddenReviewers($revision->getForbiddenReviewers()); + $iface->setDiff($diff); + + $xscript = HeraldEngine::loadAndApplyRules($iface); + $xscript_uri = $xscript->getURI(); + $xscript_phid = $xscript->getPHID(); + $xscript_header = $xscript->getXHeraldRulesHeader(); + + + $sub = array( + 'rev' => array(), + 'ccs' => $iface->getCCsAddedByHerald(), + ); + $rem_ccs = $iface->getCCsRemovedByHerald(); +*/ + // TODO! + $sub = array( + 'rev' => array(), + 'ccs' => array(), + ); + + + } else { + $sub = array( + 'rev' => array(), + 'ccs' => array(), + ); + } + + // Remove any CCs which are prevented by Herald rules. + $sub['ccs'] = array_diff_key($sub['ccs'], $rem_ccs); + $new['ccs'] = array_diff_key($new['ccs'], $rem_ccs); + + $add = array(); + $rem = array(); + $stable = array(); + foreach (array('rev', 'ccs') as $key) { + $add[$key] = array(); + if ($new[$key] !== null) { + $add[$key] += array_diff_key($new[$key], $old[$key]); + } + $add[$key] += array_diff_key($sub[$key], $old[$key]); + + $combined = $sub[$key]; + if ($new[$key] !== null) { + $combined += $new[$key]; + } + $rem[$key] = array_diff_key($old[$key], $combined); + + $stable[$key] = array_diff_key($old[$key], $add[$key] + $rem[$key]); + } + + self::removeReviewers( + $revision, + array_keys($rem['rev']), + $this->actorPHID); + self::addReviewers( + $revision, + array_keys($add['rev']), + $this->actorPHID); + + // Add the owner to the relevant set of users so they get a copy of the + // email. + if (!$this->silentUpdate) { + if ($is_new) { + $add['rev'][$this->getActorPHID()] = true; + } else { + $stable['rev'][$this->getActorPHID()] = true; + } + } + + $mail = array(); + + $changesets = null; + $feedback = null; + if ($diff) { + $changesets = $diff->loadChangesets(); + // TODO: move to DifferentialFeedbackEditor + if (!$is_new) { + // TODO +// $feedback = $this->createFeedback(); + } + if ($feedback) { + $mail[] = id(new DifferentialNewDiffMail( + $revision, + $this->getActorPHID(), + $changesets)) + ->setIsFirstMailAboutRevision($is_new) + ->setIsFirstMailToRecipients($is_new) + ->setComments($this->getComments()) + ->setToPHIDs(array_keys($stable['rev'])) + ->setCCPHIDs(array_keys($stable['ccs'])); + } + + // Save the changes we made above. + +// TODO +// $diff->setDescription(substr($this->getComments(), 0, 80)); + $diff->save(); + + // An updated diff should require review, as long as it's not committed + // or accepted. The "accepted" status is "sticky" to encourage courtesy + // re-diffs after someone accepts with minor changes/suggestions. + + $status = $revision->getStatus(); + if ($status != DifferentialRevisionStatus::COMMITTED && + $status != DifferentialRevisionStatus::ACCEPTED) { + $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); + } + + } else { + $diff = $revision->getActiveDiff(); + if ($diff) { + $changesets = id(new DifferentialChangeset())->loadAllWithDiff($diff); + } else { + $changesets = array(); + } + } + + $revision->save(); + +// TODO +// $revision->saveTransaction(); + + $event = array( + 'revision_id' => $revision->getID(), + 'PHID' => $revision->getPHID(), + 'action' => $is_new ? 'create' : 'update', + 'actor' => $this->getActorPHID(), + ); + +// TODO +// id(new ToolsTimelineEvent('difx', fb_json_encode($event)))->record(); + + if ($this->silentUpdate) { + return; + } + +// TODO +// $revision->attachReviewers(array_keys($new['rev'])); +// $revision->attachCCPHIDs(array_keys($new['ccs'])); + + if ($add['ccs'] || $rem['ccs']) { + foreach (array_keys($add['ccs']) as $id) { + if (empty($new['ccs'][$id])) { + $reason_phid = 'TODO';//$xscript_phid; + } else { + $reason_phid = $this->getActorPHID(); + } + self::addCCPHID($revision, $id, $reason_phid); + } + foreach (array_keys($rem['ccs']) as $id) { + if (empty($new['ccs'][$id])) { + $reason_phid = $this->getActorPHID(); + } else { + $reason_phid = 'TODO';//$xscript_phid; + } + self::removeCCPHID($revision, $id, $reason_phid); + } + } + + if ($add['rev']) { + $message = id(new DifferentialNewDiffMail( + $revision, + $this->getActorPHID(), + $changesets)) + ->setIsFirstMailAboutRevision($is_new) + ->setIsFirstMailToRecipients(true) + ->setToPHIDs(array_keys($add['rev'])); + + if ($is_new) { + // The first time we send an email about a revision, put the CCs in + // the "CC:" field of the same "Review Requested" email that reviewers + // get, so you don't get two initial emails if you're on a list that + // is CC'd. + $message->setCCPHIDs(array_keys($add['ccs'])); + } + + $mail[] = $message; + } + + // If you were added as a reviewer and a CC, just give you the reviewer + // email. We could go to greater lengths to prevent this, but there's + // bunch of stuff with list subscriptions anyway. You can still get two + // emails, but only if a revision is updated and you are added as a reviewer + // at the same time a list you are on is added as a CC, which is rare and + // reasonable. + $add['ccs'] = array_diff_key($add['ccs'], $add['rev']); + + if (!$is_new && $add['ccs']) { + $mail[] = id(new DifferentialCCWelcomeMail( + $revision, + $this->getActorPHID(), + $changesets)) + ->setIsFirstMailToRecipients(true) + ->setToPHIDs(array_keys($add['ccs'])); + } + + foreach ($mail as $message) { +// TODO +// $message->setHeraldTranscriptURI($xscript_uri); +// $message->setXHeraldRulesHeader($xscript_header); + $message->send(); + } + } + + public function addCCPHID( + DifferentialRevision $revision, + $phid, + $reason_phid) { + self::alterCCPHID($revision, $phid, true, $reason_phid); + } + + public function removeCCPHID( + DifferentialRevision $revision, + $phid, + $reason_phid) { + self::alterCCPHID($revision, $phid, false, $reason_phid); + } + + protected static function alterCCPHID( + DifferentialRevision $revision, + $phid, + $add, + $reason_phid) { +/* + $relationship = new DifferentialRelationship(); + $relationship->setRevisionID($revision->getID()); + $relationship->setRelation(DifferentialRelationship::RELATION_SUBSCRIBED); + $relationship->setRelatedPHID($phid); + $relationship->setForbidden(!$add); + $relationship->setReasonPHID($reason_phid); + $relationship->replace(); +*/ + } + + + public static function addReviewers( + DifferentialRevision $revision, + array $reviewer_ids, + $reason_phid) { +/* + foreach ($reviewer_ids as $reviewer_id) { + $relationship = new DifferentialRelationship(); + $relationship->setRevisionID($revision->getID()); + $relationship->setRelatedPHID($reviewer_id); + $relationship->setForbidden(false); + $relationship->setReasonPHID($reason_phid); + $relationship->setRelation(DifferentialRelationship::RELATION_REVIEWER); + $relationship->replace(); + } +*/ + } + + public static function removeReviewers( + DifferentialRevision $revision, + array $reviewer_ids, + $reason_phid) { +/* + if (!$reviewer_ids) { + return; + } + + foreach ($reviewer_ids as $reviewer_id) { + $relationship = new DifferentialRelationship(); + $relationship->setRevisionID($revision->getID()); + $relationship->setRelatedPHID($reviewer_id); + $relationship->setForbidden(true); + $relationship->setReasonPHID($reason_phid); + $relationship->setRelation(DifferentialRelationship::RELATION_REVIEWER); + $relationship->replace(); + } +*/ + } + +/* + protected function createFeedback() { + $revision = $this->getRevision(); + $feedback = id(new DifferentialFeedback()) + ->setUserID($this->getActorPHID()) + ->setRevision($revision) + ->setContent($this->getComments()) + ->setAction('update'); + + $feedback->save(); + + return $feedback; + } +*/ + +} + diff --git a/src/applications/differential/editor/revision/__init__.php b/src/applications/differential/editor/revision/__init__.php new file mode 100644 index 0000000000..1be89fe9a6 --- /dev/null +++ b/src/applications/differential/editor/revision/__init__.php @@ -0,0 +1,17 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus'); +phutil_require_module('phabricator', 'applications/differential/mail/ccwelcome'); +phutil_require_module('phabricator', 'applications/differential/mail/newdiff'); +phutil_require_module('phabricator', 'applications/differential/storage/changeset'); + +phutil_require_module('phutil', 'utils'); + + +phutil_require_source('DifferentialRevisionEditor.php'); diff --git a/src/applications/differential/mail/base/DifferentialMail.php b/src/applications/differential/mail/base/DifferentialMail.php new file mode 100755 index 0000000000..6b30df6fb8 --- /dev/null +++ b/src/applications/differential/mail/base/DifferentialMail.php @@ -0,0 +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 $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; + return $this; + } + + public function getFeedback() { + return $this->feedback; + } + + 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/base/__init__.php b/src/applications/differential/mail/base/__init__.php new file mode 100644 index 0000000000..e37c404c5a --- /dev/null +++ b/src/applications/differential/mail/base/__init__.php @@ -0,0 +1,12 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'applications/metamta/storage/mail'); + + +phutil_require_source('DifferentialMail.php'); diff --git a/src/view/form/control/textarea/AphrontFormTextAreaControl.php b/src/applications/differential/mail/ccwelcome/DifferentialCCWelcomeMail.php similarity index 54% copy from src/view/form/control/textarea/AphrontFormTextAreaControl.php copy to src/applications/differential/mail/ccwelcome/DifferentialCCWelcomeMail.php index 2668b825b1..1cf42fd5a1 100755 --- a/src/view/form/control/textarea/AphrontFormTextAreaControl.php +++ b/src/applications/differential/mail/ccwelcome/DifferentialCCWelcomeMail.php @@ -1,35 +1,39 @@ <?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 AphrontFormTextAreaControl extends AphrontFormControl { +class DifferentialCCWelcomeMail extends DifferentialReviewRequestMail { - protected function getCustomControlClass() { - return 'aphront-form-control-textarea'; + protected function renderSubject() { + $revision = $this->getRevision(); + return 'Added to CC: '.$revision->getName(); } - protected function renderInput() { - return phutil_render_tag( - 'textarea', - array( - 'name' => $this->getName(), - 'disabled' => $this->getDisabled() ? 'disabled' : null, - ), - phutil_escape_html($this->getValue())); - } + protected function renderBody() { + + $actor = $this->getActorName(); + $name = $this->getRevision()->getName(); + $body = array(); + + $body[] = "{$actor} added you to the CC list for the revision \"{$name}\"."; + $body[] = null; + $body[] = $this->renderReviewRequestBody(); + + return implode("\n", $body); + } } diff --git a/src/applications/differential/mail/ccwelcome/__init__.php b/src/applications/differential/mail/ccwelcome/__init__.php new file mode 100644 index 0000000000..0a59f308d4 --- /dev/null +++ b/src/applications/differential/mail/ccwelcome/__init__.php @@ -0,0 +1,12 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'applications/differential/mail/reviewrequest'); + + +phutil_require_source('DifferentialCCWelcomeMail.php'); diff --git a/src/applications/differential/storage/revision/DifferentialRevision.php b/src/applications/differential/mail/diffcontent/DifferentialDiffContentMail.php similarity index 60% copy from src/applications/differential/storage/revision/DifferentialRevision.php copy to src/applications/differential/mail/diffcontent/DifferentialDiffContentMail.php index 4a24911c63..002569be64 100755 --- a/src/applications/differential/storage/revision/DifferentialRevision.php +++ b/src/applications/differential/mail/diffcontent/DifferentialDiffContentMail.php @@ -1,36 +1,35 @@ <?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 DifferentialRevision extends DifferentialDAO { +class DifferentialDiffContentMail extends DifferentialMail { - protected $name; - protected $status; + protected $content; - protected $summary; - protected $testPlan; - protected $revertPlan; - protected $blameRevision; + public function __construct(DifferentialRevision $revision, $content) { + $this->setRevision($revision); + $this->content = $content; + } - protected $phid; - protected $ownerPHID; - - protected $dateCommitted; - - protected $lineCount; + protected function renderSubject() { + return "Content: ".$this->getRevision()->getName(); + } + protected function renderBody() { + return $this->content; + } } diff --git a/src/applications/differential/mail/diffcontent/__init__.php b/src/applications/differential/mail/diffcontent/__init__.php new file mode 100644 index 0000000000..2398125b9a --- /dev/null +++ b/src/applications/differential/mail/diffcontent/__init__.php @@ -0,0 +1,12 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'applications/differential/mail/base'); + + +phutil_require_source('DifferentialDiffContentMail.php'); diff --git a/src/applications/differential/mail/feedback/DifferentialFeedbackMail.php b/src/applications/differential/mail/feedback/DifferentialFeedbackMail.php new file mode 100755 index 0000000000..c3715126fd --- /dev/null +++ b/src/applications/differential/mail/feedback/DifferentialFeedbackMail.php @@ -0,0 +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 { + + 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, + array $changesets, + array $inline_comments) { + + $this->setRevision($revision); + $this->setActorID($actor_id); + $this->setFeedback($feedback); + $this->setChangesets($changesets); + $this->setInlineComments($inline_comments); + + } + + protected function renderSubject() { + $revision = $this->getRevision(); + $verb = $this->getVerb(); + return ucwords($verb).': '.$revision->getName(); + } + + protected function getVerb() { + $feedback = $this->getFeedback(); + $action = $feedback->getAction(); + $verb = DifferentialAction::getActionVerb($action); + return $verb; + } + + protected function renderBody() { + + $feedback = $this->getFeedback(); + + $actor = $this->getActorName(); + $name = $this->getRevision()->getName(); + $verb = $this->getVerb(); + + $body = array(); + + $body[] = "{$actor} has {$verb} the revision \"{$name}\"."; + $body[] = null; + + $content = $feedback->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/feedback/__init__.php new file mode 100644 index 0000000000..dd323e6309 --- /dev/null +++ b/src/applications/differential/mail/feedback/__init__.php @@ -0,0 +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'); diff --git a/src/applications/differential/mail/newdiff/DifferentialNewDiffMail.php b/src/applications/differential/mail/newdiff/DifferentialNewDiffMail.php new file mode 100755 index 0000000000..82fcbe9b1a --- /dev/null +++ b/src/applications/differential/mail/newdiff/DifferentialNewDiffMail.php @@ -0,0 +1,65 @@ +<?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 DifferentialNewDiffMail extends DifferentialReviewRequestMail { + + protected function renderSubject() { + $revision = $this->getRevision(); + $line_count = $revision->getLineCount(); + $lines = ($line_count == 1 ? "1 line" : "{$line_count} lines"); + + if ($this->isFirstMailToRecipients()) { + $verb = 'Request'; + } else { + $verb = 'Updated'; + } + + return "{$verb} ({$lines}): ".$revision->getTitle(); + } + + protected function buildSubject() { + if (!$this->isFirstMailToRecipients()) { + return parent::buildSubject(); + } + + $prefix = self::SUBJECT_PREFIX; + + $subject = $this->renderSubject(); + + return "{$prefix} {$subject}"; + } + + protected function renderBody() { + $actor = $this->getActorName(); + + $name = $this->getRevision()->getTitle(); + + $body = array(); + + if ($this->isFirstMailToRecipients()) { + $body[] = "{$actor} requested code review of \"{$name}\"."; + } else { + $body[] = "{$actor} updated the revision \"{$name}\"."; + } + $body[] = null; + + $body[] = $this->renderReviewRequestBody(); + + return implode("\n", $body); + } +} diff --git a/src/applications/differential/mail/newdiff/__init__.php b/src/applications/differential/mail/newdiff/__init__.php new file mode 100644 index 0000000000..f915929352 --- /dev/null +++ b/src/applications/differential/mail/newdiff/__init__.php @@ -0,0 +1,12 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'applications/differential/mail/reviewrequest'); + + +phutil_require_source('DifferentialNewDiffMail.php'); diff --git a/src/applications/differential/mail/reviewrequest/DifferentialReviewRequestMail.php b/src/applications/differential/mail/reviewrequest/DifferentialReviewRequestMail.php new file mode 100755 index 0000000000..5589a9b4a9 --- /dev/null +++ b/src/applications/differential/mail/reviewrequest/DifferentialReviewRequestMail.php @@ -0,0 +1,74 @@ +<?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 DifferentialReviewRequestMail extends DifferentialMail { + + protected $comments; + + public function setComments($comments) { + $this->comments = $comments; + return $this; + } + + public function getComments() { + return $this->comments; + } + + public function __construct( + DifferentialRevision $revision, + $actor_id, + array $changesets) { + + $this->setRevision($revision); + $this->setActorID($actor_id); + $this->setChangesets($changesets); + } + + protected function renderReviewRequestBody() { + $revision = $this->getRevision(); + + $body = array(); + if ($this->isFirstMailToRecipients()) { + $body[] = $this->formatText($revision->getSummary()); + $body[] = null; + + $body[] = 'TEST PLAN'; + $body[] = $this->formatText($revision->getTestPlan()); + $body[] = null; + } else { + if (strlen($this->getComments())) { + $body[] = $this->formatText($this->getComments()); + $body[] = null; + } + } + + $body[] = $this->renderRevisionDetailLink(); + $body[] = null; + + $changesets = $this->getChangesets(); + if ($changesets) { + $body[] = 'AFFECTED FILES'; + foreach ($changesets as $changeset) { + $body[] = ' '.$changeset->getFilename(); + } + $body[] = null; + } + + return implode("\n", $body); + } +} diff --git a/src/applications/differential/mail/reviewrequest/__init__.php b/src/applications/differential/mail/reviewrequest/__init__.php new file mode 100644 index 0000000000..2411be0156 --- /dev/null +++ b/src/applications/differential/mail/reviewrequest/__init__.php @@ -0,0 +1,12 @@ +<?php +/** + * This file is automatically generated. Lint this module to rebuild it. + * @generated + */ + + + +phutil_require_module('phabricator', 'applications/differential/mail/base'); + + +phutil_require_source('DifferentialReviewRequestMail.php'); diff --git a/src/applications/differential/storage/revision/DifferentialRevision.php b/src/applications/differential/storage/revision/DifferentialRevision.php index 4a24911c63..5f3b6008ca 100755 --- a/src/applications/differential/storage/revision/DifferentialRevision.php +++ b/src/applications/differential/storage/revision/DifferentialRevision.php @@ -1,36 +1,58 @@ <?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 DifferentialRevision extends DifferentialDAO { - protected $name; + protected $title; protected $status; protected $summary; protected $testPlan; protected $revertPlan; protected $blameRevision; protected $phid; protected $ownerPHID; protected $dateCommitted; protected $lineCount; + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID('DREV'); + } + + public function loadRelationships() { + + } + + public function getReviewers() { + return array(); + } + + public function getCCPHIDs() { + return array(); + } } diff --git a/src/view/form/base/AphrontFormView.php b/src/view/form/base/AphrontFormView.php index 25a051f5c3..77aa14f64d 100755 --- a/src/view/form/base/AphrontFormView.php +++ b/src/view/form/base/AphrontFormView.php @@ -1,73 +1,81 @@ <?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 AphrontFormView extends AphrontView { private $action; private $method = 'POST'; private $header; private $data = array(); private $encType; public function setAction($action) { $this->action = $action; return $this; } public function setMethod($method) { $this->method = $method; return $this; } public function setEncType($enc_type) { $this->encType = $enc_type; return $this; } + public function addHiddenInput($key, $value) { + $this->data[$key] = $value; + return $this; + } + public function render() { require_celerity_resource('aphront-form-view-css'); return phutil_render_tag( 'form', array( 'action' => $this->action, 'method' => $this->method, 'class' => 'aphront-form-view', 'enctype' => $this->encType, ), $this->renderDataInputs(). $this->renderChildren()); } private function renderDataInputs() { $data = $this->data + array( '__form__' => 1, ); $inputs = array(); foreach ($data as $key => $value) { + if ($value === null) { + continue; + } $inputs[] = phutil_render_tag( 'input', array( 'type' => 'hidden', 'name' => $key, 'value' => $value, )); } return implode("\n", $inputs); } } diff --git a/src/view/form/control/textarea/AphrontFormTextAreaControl.php b/src/view/form/control/textarea/AphrontFormTextAreaControl.php index 2668b825b1..55ec143551 100755 --- a/src/view/form/control/textarea/AphrontFormTextAreaControl.php +++ b/src/view/form/control/textarea/AphrontFormTextAreaControl.php @@ -1,35 +1,55 @@ <?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 AphrontFormTextAreaControl extends AphrontFormControl { + const HEIGHT_VERY_SHORT = 'very-short'; + const HEIGHT_SHORT = 'short'; + + private $height; + + public function setHeight($height) { + $this->height = $height; + return $this; + } + protected function getCustomControlClass() { return 'aphront-form-control-textarea'; } protected function renderInput() { + + $height_class = null; + switch ($this->height) { + case self::HEIGHT_VERY_SHORT: + case self::HEIGHT_SHORT: + $height_class = 'aphront-textarea-'.$this->height; + break; + } + return phutil_render_tag( 'textarea', array( 'name' => $this->getName(), 'disabled' => $this->getDisabled() ? 'disabled' : null, + 'class' => $height_class, ), phutil_escape_html($this->getValue())); } } diff --git a/src/view/page/standard/PhabricatorStandardPageView.php b/src/view/page/standard/PhabricatorStandardPageView.php index 8f3adddbfc..7956b4ba2b 100755 --- a/src/view/page/standard/PhabricatorStandardPageView.php +++ b/src/view/page/standard/PhabricatorStandardPageView.php @@ -1,151 +1,152 @@ <?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 PhabricatorStandardPageView extends AphrontPageView { private $baseURI; private $applicationName; private $tabs = array(); private $selectedTab; private $glyph; private $bodyContent; private $request; public function setRequest($request) { $this->request = $request; return $this; } public function getRequest() { return $this->request; } public function setApplicationName($application_name) { $this->applicationName = $application_name; return $this; } public function getApplicationName() { return $this->applicationName; } public function setBaseURI($base_uri) { $this->baseURI = $base_uri; return $this; } public function getBaseURI() { return $this->baseURI; } public function setTabs(array $tabs, $selected_tab) { $this->tabs = $tabs; $this->selectedTab = $selected_tab; return $this; } public function getTitle() { return $this->getGlyph().' '.parent::getTitle(); } protected function willRenderPage() { require_celerity_resource('phabricator-core-css'); require_celerity_resource('phabricator-core-buttons-css'); require_celerity_resource('phabricator-standard-page-view'); require_celerity_resource('javelin-lib-dev'); $this->bodyContent = $this->renderChildren(); } protected function getHead() { $response = CelerityAPI::getStaticResourceResponse(); return $response->renderResourcesOfType('css'). '<script type="text/javascript">window.__DEV__=1;</script>'. '<script type="text/javascript" src="/rsrc/js/javelin/init.dev.js">'. '</script>'; } public function setGlyph($glyph) { $this->glyph = $glyph; return $this; } public function getGlyph() { return $this->glyph; } protected function getBody() { $tabs = array(); foreach ($this->tabs as $name => $tab) { $tabs[] = phutil_render_tag( 'a', array( 'href' => idx($tab, 'href'), 'class' => ($name == $this->selectedTab) ? 'phabricator-selected-tab' : null, ), phutil_escape_html(idx($tab, 'name'))); } $tabs = implode('', $tabs); if ($tabs) { $tabs = '<span class="phabricator-head-tabs">'.$tabs.'</span>'; } $login_stuff = null; $request = $this->getRequest(); - $user = $request->getUser(); - - if ($user->getPHID()) { - $login_stuff = 'Logged in as '.phutil_escape_html($user->getUsername()); + if ($request) { + $user = $request->getUser(); + if ($user->getPHID()) { + $login_stuff = 'Logged in as '.phutil_escape_html($user->getUsername()); + } } return '<div class="phabricator-standard-page">'. '<div class="phabricator-standard-header">'. '<div class="phabricator-login-details">'. $login_stuff. '</div>'. '<a href="/">Phabricator</a> '. phutil_render_tag( 'a', array( 'href' => $this->getBaseURI(), 'class' => 'phabricator-head-appname', ), phutil_escape_html($this->getApplicationName())). $tabs. '</div>'. $this->bodyContent. '<div style="clear: both;"></div>'. '</div>'; } protected function getTail() { $response = CelerityAPI::getStaticResourceResponse(); return $response->renderResourcesOfType('js'). $response->renderHTMLFooter(); } } diff --git a/webroot/rsrc/css/aphront/form-view.css b/webroot/rsrc/css/aphront/form-view.css index fdf79ef46b..aded181c7d 100644 --- a/webroot/rsrc/css/aphront/form-view.css +++ b/webroot/rsrc/css/aphront/form-view.css @@ -1,99 +1,103 @@ /** * @provides aphront-form-view-css */ .aphront-form-view { background: #e7e7e7; border: 1px solid #c4c4c4; padding: 1em; } .aphront-form-view label.aphront-form-label { padding-top: 4px; width: 19%; float: left; text-align: right; font-weight: bold; font-size: 13px; color: #666666; } .aphront-form-input { margin-left: 20%; margin-right: 25%; width: 55%; } .aphront-form-error { width: 23%; float: right; color: #aa0000; font-weight: bold; padding-top: 4px; } .aphront-form-input input, .aphront-form-input textarea { font-size: 12px; width: 100%; } .aphront-form-input textarea { height: 12em; } .aphront-form-control { padding: 4px; } .aphront-form-control-submit button, .aphront-form-control-submit a.button { float: right; margin: 0.5em 0 0em 2%; } +.aphront-form-control-textarea textarea.aphront-textarea-very-short { + height: 3em; +} + .aphront-form-control-select .aphront-form-input { padding-top: 2px; } .aphront-form-view .aphront-form-caption { font-size: 11px; color: #444444; text-align: right; clear: both; margin-right: 25%; margin-left: 20%; } .aphront-error-view { width: 720px; margin: 1em auto; border: 1px solid #aa0000; padding: 1em; background: #f9b9bc; } .aphront-form-instructions { margin: 0.75em 3% 1.25em; } .aphront-form-control-static .aphront-form-input { padding-top: 4px; font-size: 13px; } table.aphront-form-control-checkbox-layout { margin-top: 3px; font-size: 13px; } table.aphront-form-control-checkbox-layout th { padding-top: 2px; padding-left: 0.35em; } .aphront-form-control-checkbox-layout td input { width: auto; }