From 148a50e48be146b88036032d1cad772d2627da28 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 12 Mar 2016 13:02:32 -0800 Subject: [PATCH 01/25] Convert Differential to new layout Summary: First pass at converting Differential, I likely have some buggy-poos but thought I'd toss this up now in case very bad bugs present. To do: - Need to put status back on Hovercards - "Diff Detail" probably needs a better design Test Plan: Looking at lots of diffs, admittedly I dont have harbormaster, etc, running locally. Checked Diffusion for Table of Content changes on small and large commits. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15463 --- resources/celerity/map.php | 30 +- src/__phutil_library_map__.php | 4 - .../controller/DifferentialController.php | 3 +- .../DifferentialRevisionViewController.php | 412 ++++++++++-------- .../customfield/DifferentialAuthorField.php | 2 +- .../DifferentialHovercardEngineExtension.php | 2 - .../view/DifferentialAddCommentView.php | 2 +- .../view/DifferentialChangesetListView.php | 7 + .../view/DifferentialLocalCommitsView.php | 1 + .../view/DifferentialPrimaryPaneView.php | 23 - .../view/DifferentialRevisionDetailView.php | 121 ----- .../view/DifferentialRevisionListView.php | 7 + .../DifferentialRevisionUpdateHistoryView.php | 2 +- .../controller/DiffusionCommitController.php | 34 +- .../view/HarbormasterUnitSummaryView.php | 19 +- .../view/PHUIDiffTableOfContentsListView.php | 35 +- .../differential/changeset-view.css | 10 +- .../css/application/differential/core.css | 6 +- webroot/rsrc/css/phui/phui-box.css | 4 - webroot/rsrc/css/phui/phui-object-box.css | 10 + .../rsrc/css/phui/phui-two-column-view.css | 4 +- 21 files changed, 361 insertions(+), 377 deletions(-) delete mode 100644 src/applications/differential/view/DifferentialPrimaryPaneView.php delete mode 100644 src/applications/differential/view/DifferentialRevisionDetailView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fdc6357f60..d05a7b4903 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,10 +7,10 @@ */ return array( 'names' => array( - 'core.pkg.css' => '9c8e888d', + 'core.pkg.css' => 'c6ad5231', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '7d0a63a7', + 'differential.pkg.css' => '7ba78475', 'differential.pkg.js' => 'd0cd0df6', 'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.js' => '3a9a8bfa', @@ -57,8 +57,8 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', - 'rsrc/css/application/differential/core.css' => '7ac3cabc', + 'rsrc/css/application/differential/changeset-view.css' => '3e3b0b76', + 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', @@ -123,7 +123,7 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '3830ab21', + 'rsrc/css/phui/phui-box.css' => '06153ae3', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', @@ -144,7 +144,7 @@ return array( 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', 'rsrc/css/phui/phui-list.css' => '9da2aa00', - 'rsrc/css/phui/phui-object-box.css' => '91628842', + 'rsrc/css/phui/phui-object-box.css' => '6b487c57', 'rsrc/css/phui/phui-object-item-list-view.css' => '18b2ce8e', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'a0173eba', - 'rsrc/css/phui/phui-two-column-view.css' => 'e6bf86b6', + 'rsrc/css/phui/phui-two-column-view.css' => '61dd6d38', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -545,8 +545,8 @@ return array( 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'b6b0d1bb', - 'differential-core-view-css' => '7ac3cabc', + 'differential-changeset-view-css' => '3e3b0b76', + 'differential-core-view-css' => '5b7b8ff4', 'differential-inline-comment-editor' => '64a5550f', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -805,7 +805,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '3830ab21', + 'phui-box-css' => '06153ae3', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', @@ -833,7 +833,7 @@ return array( 'phui-info-view-css' => '6d7c3509', 'phui-inline-comment-view-css' => '5953c28e', 'phui-list-view-css' => '9da2aa00', - 'phui-object-box-css' => '91628842', + 'phui-object-box-css' => '6b487c57', 'phui-object-item-list-view-css' => '18b2ce8e', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', @@ -846,7 +846,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'a0173eba', - 'phui-two-column-view-css' => 'e6bf86b6', + 'phui-two-column-view-css' => '61dd6d38', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', @@ -1124,6 +1124,9 @@ return array( 'javelin-util', 'javelin-uri', ), + '3e3b0b76' => array( + 'phui-inline-comment-view-css', + ), '3f5d6dbf' => array( 'javelin-behavior', 'javelin-dom', @@ -1791,9 +1794,6 @@ return array( 'javelin-json', 'phabricator-draggable-list', ), - 'b6b0d1bb' => array( - 'phui-inline-comment-view-css', - ), 'bae58312' => array( 'javelin-install', 'javelin-workboard-card', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bfad24494a..f253b328b2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -473,7 +473,6 @@ phutil_register_library_map(array( 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', 'DifferentialPathField' => 'applications/differential/customfield/DifferentialPathField.php', - 'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php', 'DifferentialProjectReviewersField' => 'applications/differential/customfield/DifferentialProjectReviewersField.php', 'DifferentialProjectsField' => 'applications/differential/customfield/DifferentialProjectsField.php', 'DifferentialQueryConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryConduitAPIMethod.php', @@ -508,7 +507,6 @@ phutil_register_library_map(array( 'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', - 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', @@ -4580,7 +4578,6 @@ phutil_register_library_map(array( 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', 'DifferentialPathField' => 'DifferentialCustomField', - 'DifferentialPrimaryPaneView' => 'AphrontView', 'DifferentialProjectReviewersField' => 'DifferentialCustomField', 'DifferentialProjectsField' => 'DifferentialCoreCustomField', 'DifferentialQueryConduitAPIMethod' => 'DifferentialConduitAPIMethod', @@ -4630,7 +4627,6 @@ phutil_register_library_map(array( 'DifferentialRevisionControlSystem' => 'Phobject', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', - 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 1aba876c68..43252c07f6 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -28,7 +28,8 @@ abstract class DifferentialController extends PhabricatorController { $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $have_owners = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorOwnersApplication', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index cad2932a0c..c59ee04047 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -40,7 +40,6 @@ final class DifferentialRevisionViewController extends DifferentialController { $revision->attachActiveDiff(last($diffs)); $diff_vs = $request->getInt('vs'); - $target_id = $request->getInt('id'); $target = idx($diffs, $target_id, end($diffs)); @@ -210,14 +209,10 @@ final class DifferentialRevisionViewController extends DifferentialController { $commits_for_links = array(); } - $revision_detail = id(new DifferentialRevisionDetailView()) - ->setUser($viewer) - ->setRevision($revision) - ->setDiff(end($diffs)) - ->setCustomFields($field_list) - ->setURI($request->getRequestURI()); - - $actions = $this->getRevisionActions($revision); + $header = $this->buildHeader($revision); + $subheader = $this->buildSubheaderView($revision); + $details = $this->buildDetails($revision, $field_list); + $curtain = $this->buildCurtain($revision); $whitespace = $request->getStr( 'whitespace', @@ -232,21 +227,16 @@ final class DifferentialRevisionViewController extends DifferentialController { $symbol_indexes = array(); } - $revision_detail->setActions($actions); - $revision_detail->setUser($viewer); - - $revision_detail_box = $revision_detail->render(); - $revision_warnings = $this->buildRevisionWarnings( $revision, $field_list, $warning_handle_map, $handles); + $info_view = null; if ($revision_warnings) { - $revision_warnings = id(new PHUIInfoView()) + $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors($revision_warnings); - $revision_detail_box->setInfoView($revision_warnings); } $detail_diffs = array_select_keys( @@ -277,39 +267,31 @@ final class DifferentialRevisionViewController extends DifferentialController { $comment_view->setQuoteTargetID('comment-content'); } - $wrap_id = celerity_generate_unique_node_id(); - $comment_view = phutil_tag( - 'div', - array( - 'id' => $wrap_id, - ), - $comment_view); + $changeset_view = id(new DifferentialChangesetListView()) + ->setChangesets($changesets) + ->setVisibleChangesets($visible_changesets) + ->setStandaloneURI('/differential/changeset/') + ->setRawFileURIs( + '/differential/changeset/?view=old', + '/differential/changeset/?view=new') + ->setUser($viewer) + ->setDiff($target) + ->setRenderingReferences($rendering_references) + ->setVsMap($vs_map) + ->setWhitespace($whitespace) + ->setSymbolIndexes($symbol_indexes) + ->setTitle(pht('Diff %s', $target->getID())) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $changeset_view = new DifferentialChangesetListView(); - $changeset_view->setChangesets($changesets); - $changeset_view->setVisibleChangesets($visible_changesets); + if ($repository) { + $changeset_view->setRepository($repository); + } if (!$viewer_is_anonymous) { $changeset_view->setInlineCommentControllerURI( '/differential/comment/inline/edit/'.$revision->getID().'/'); } - $changeset_view->setStandaloneURI('/differential/changeset/'); - $changeset_view->setRawFileURIs( - '/differential/changeset/?view=old', - '/differential/changeset/?view=new'); - - $changeset_view->setUser($viewer); - $changeset_view->setDiff($target); - $changeset_view->setRenderingReferences($rendering_references); - $changeset_view->setVsMap($vs_map); - $changeset_view->setWhitespace($whitespace); - if ($repository) { - $changeset_view->setRepository($repository); - } - $changeset_view->setSymbolIndexes($symbol_indexes); - $changeset_view->setTitle(pht('Diff %s', $target->getID())); - $diff_history = id(new DifferentialRevisionUpdateHistoryView()) ->setUser($viewer) ->setDiffs($diffs) @@ -344,71 +326,9 @@ final class DifferentialRevisionViewController extends DifferentialController { $comment_form = null; if (!$viewer_is_anonymous) { - $draft = id(new PhabricatorDraft())->loadOneWhere( - 'authorPHID = %s AND draftKey = %s', - $viewer->getPHID(), - 'differential-comment-'.$revision->getID()); - - $reviewers = array(); - $ccs = array(); - if ($draft) { - $reviewers = idx($draft->getMetadata(), 'reviewers', array()); - $ccs = idx($draft->getMetadata(), 'ccs', array()); - if ($reviewers || $ccs) { - $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); - $reviewers = array_select_keys($handles, $reviewers); - $ccs = array_select_keys($handles, $ccs); - } - } - - $comment_form = new DifferentialAddCommentView(); - $comment_form->setRevision($revision); - - $review_warnings = array(); - foreach ($field_list->getFields() as $field) { - $review_warnings[] = $field->getWarningsForDetailView(); - } - $review_warnings = array_mergev($review_warnings); - - if ($review_warnings) { - $review_warnings_panel = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors($review_warnings); - $comment_form->setInfoView($review_warnings_panel); - } - - $comment_form->setActions($this->getRevisionCommentActions($revision)); - $action_uri = $this->getApplicationURI( - 'comment/save/'.$revision->getID().'/'); - - $comment_form->setActionURI($action_uri); - $comment_form->setUser($viewer); - $comment_form->setDraft($draft); - $comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')); - $comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID')); - - // TODO: This just makes the "Z" key work. Generalize this and remove - // it at some point. - $comment_form = phutil_tag( - 'div', - array( - 'class' => 'differential-add-comment-panel', - ), - $comment_form); + $comment_form = $this->buildCommentForm($revision, $field_list); } - $pane_id = celerity_generate_unique_node_id(); - Javelin::initBehavior( - 'differential-keyboard-navigation', - array( - 'haunt' => $pane_id, - )); - Javelin::initBehavior('differential-user-select'); - - $page_pane = id(new DifferentialPrimaryPaneView()) - ->setID($pane_id) - ->appendChild($comment_view); - $signatures = DifferentialRequiredSignaturesField::loadForRevision( $revision); $missing_signatures = false; @@ -418,21 +338,17 @@ final class DifferentialRevisionViewController extends DifferentialController { } } + $footer = array(); + $signature_message = null; if ($missing_signatures) { $signature_message = id(new PHUIInfoView()) - ->setErrors( - array( - array( - phutil_tag('strong', array(), pht('Content Hidden:')), - ' ', - pht( - 'The content of this revision is hidden until the author has '. - 'signed all of the required legal agreements.'), - ), - )); - $page_pane->appendChild($signature_message); + ->setTitle(pht('Content Hidden')) + ->appendChild( + pht( + 'The content of this revision is hidden until the author has '. + 'signed all of the required legal agreements.')); } else { - $page_pane->appendChild( + $footer[] = array( $diff_history, $warning, @@ -440,37 +356,28 @@ final class DifferentialRevisionViewController extends DifferentialController { $toc_view, $other_view, $changeset_view, - )); + ); } if ($comment_form) { - $page_pane->appendChild($comment_form); + $footer[] = $comment_form; } else { // TODO: For now, just use this to get "Login to Comment". - $page_pane->appendChild( - id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setRequestURI($request->getRequestURI())); + $footer[] = id(new PhabricatorApplicationTransactionCommentView()) + ->setUser($viewer) + ->setRequestURI($request->getRequestURI()); } $object_id = 'D'.$revision->getID(); - $operations_box = $this->buildOperationsBox($revision); - $content = array( - $operations_box, - $revision_detail_box, - $diff_detail_box, - $unit_box, - $page_pane, - ); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($object_id, '/'.$object_id); + $crumbs->setBorder(true); $prefs = $viewer->loadPreferences(); - $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; + $nav = null; if ($prefs->getPreference($pref_filetree)) { $collapsed = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED, @@ -481,15 +388,38 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setBaseURI(new PhutilURI('/D'.$revision->getID())) ->setCollapsed((bool)$collapsed) ->build($changesets); - } else { - $nav = null; } - $page = $this->newPage() + // Haunt Mode + $pane_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( + 'differential-keyboard-navigation', + array( + 'haunt' => $pane_id, + )); + Javelin::initBehavior('differential-user-select'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setCurtain($curtain) + ->setID($pane_id) + ->setMainColumn(array( + $operations_box, + $info_view, + $details, + $diff_detail_box, + $unit_box, + $comment_view, + $signature_message, + )) + ->setFooter($footer); + + $page = $this->newPage() ->setTitle($object_id.' '.$revision->getTitle()) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($revision->getPHID())) - ->appendChild($content); + ->appendChild($view); if ($nav) { $page->setNavigation($nav); @@ -498,59 +428,183 @@ final class DifferentialRevisionViewController extends DifferentialController { return $page; } - private function getRevisionActions(DifferentialRevision $revision) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader(DifferentialRevision $revision) { + $view = id(new PHUIHeaderView()) + ->setHeader($revision->getTitle($revision)) + ->setUser($this->getViewer()) + ->setPolicyObject($revision) + ->setHeaderIcon('fa-cog'); + + $status = $revision->getStatus(); + $status_name = + DifferentialRevisionStatus::renderFullDescription($status); + + $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); + + return $view; + } + + private function buildSubheaderView(DifferentialRevision $revision) { + $viewer = $this->getViewer(); + + $author_phid = $revision->getAuthorPHID(); + + $author = $viewer->renderHandle($author_phid)->render(); + $date = phabricator_datetime($revision->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + + $content = pht('Authored by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + private function buildDetails( + DifferentialRevision $revision, + $custom_fields) { + $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + if ($custom_fields) { + $custom_fields->appendFieldsToPropertyList( + $revision, + $viewer, + $properties); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('DETAILS')); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } + + private function buildCurtain(DifferentialRevision $revision) { + $viewer = $this->getViewer(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); + $curtain = $this->newCurtainView($revision); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $revision, PhabricatorPolicyCapability::CAN_EDIT); - $actions = array(); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setHref("/differential/revision/edit/{$revision_id}/") + ->setName(pht('Edit Revision')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setHref("/differential/revision/edit/{$revision_id}/") - ->setName(pht('Edit Revision')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-upload') - ->setHref("/differential/revision/update/{$revision_id}/") - ->setName(pht('Update Diff')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-upload') + ->setHref("/differential/revision/update/{$revision_id}/") + ->setName(pht('Update Diff')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $this->requireResource('phabricator-object-selector-css'); $this->requireResource('javelin-behavior-phabricator-object-selector'); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-link') - ->setName(pht('Edit Dependencies')) - ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") - ->setWorkflow(true) - ->setDisabled(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-link') + ->setName(pht('Edit Dependencies')) + ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") + ->setWorkflow(true) + ->setDisabled(!$can_edit)); $maniphest = 'PhabricatorManiphestApplication'; if (PhabricatorApplication::isClassInstalled($maniphest)) { - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-anchor') - ->setName(pht('Edit Maniphest Tasks')) - ->setHref("/search/attach/{$revision_phid}/TASK/") - ->setWorkflow(true) - ->setDisabled(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-anchor') + ->setName(pht('Edit Maniphest Tasks')) + ->setHref("/search/attach/{$revision_phid}/TASK/") + ->setWorkflow(true) + ->setDisabled(!$can_edit)); } $request_uri = $this->getRequest()->getRequestURI(); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-download') - ->setName(pht('Download Raw Diff')) - ->setHref($request_uri->alter('download', 'true')); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-download') + ->setName(pht('Download Raw Diff')) + ->setHref($request_uri->alter('download', 'true'))); - return $actions; + return $curtain; + } + + private function buildCommentForm( + DifferentialRevision $revision, + $field_list) { + + $viewer = $this->getViewer(); + + $draft = id(new PhabricatorDraft())->loadOneWhere( + 'authorPHID = %s AND draftKey = %s', + $viewer->getPHID(), + 'differential-comment-'.$revision->getID()); + + $reviewers = array(); + $ccs = array(); + if ($draft) { + $reviewers = idx($draft->getMetadata(), 'reviewers', array()); + $ccs = idx($draft->getMetadata(), 'ccs', array()); + if ($reviewers || $ccs) { + $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); + $reviewers = array_select_keys($handles, $reviewers); + $ccs = array_select_keys($handles, $ccs); + } + } + + $comment_form = id(new DifferentialAddCommentView()) + ->setRevision($revision); + + $review_warnings = array(); + foreach ($field_list->getFields() as $field) { + $review_warnings[] = $field->getWarningsForDetailView(); + } + $review_warnings = array_mergev($review_warnings); + + if ($review_warnings) { + $review_warnings_panel = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($review_warnings); + $comment_form->setInfoView($review_warnings_panel); + } + + $action_uri = $this->getApplicationURI( + 'comment/save/'.$revision->getID().'/'); + + $comment_form->setActions($this->getRevisionCommentActions($revision)) + ->setActionURI($action_uri) + ->setUser($viewer) + ->setDraft($draft) + ->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')) + ->setCCs(mpull($ccs, 'getFullName', 'getPHID')); + + // TODO: This just makes the "Z" key work. Generalize this and remove + // it at some point. + $comment_form = phutil_tag( + 'div', + array( + 'class' => 'differential-add-comment-panel', + ), + $comment_form); + return $comment_form; } private function getRevisionCommentActions(DifferentialRevision $revision) { @@ -558,7 +612,7 @@ final class DifferentialRevisionViewController extends DifferentialController { DifferentialAction::ACTION_COMMENT => true, ); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $viewer_phid = $viewer->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); @@ -814,11 +868,12 @@ final class DifferentialRevisionViewController extends DifferentialController { $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Similar Open Revisions')); + ->setHeader(pht('Recent Similar Revisions')); $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); @@ -845,7 +900,7 @@ final class DifferentialRevisionViewController extends DifferentialController { assert_instances_of($changesets, 'DifferentialChangeset'); assert_instances_of($vs_changesets, 'DifferentialChangeset'); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); id(new DifferentialHunkQuery()) ->setViewer($viewer) @@ -978,7 +1033,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Diff Detail')) + ->setHeaderText(pht('DIFF DETAIL')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); $last_tab = null; @@ -1061,7 +1117,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box_view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Active Operations')); + ->setHeaderText(pht('ACTIVE OPERATIONS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return id(new DrydockRepositoryOperationStatusView()) ->setUser($viewer) @@ -1078,10 +1135,6 @@ final class DifferentialRevisionViewController extends DifferentialController { return null; } - if (!$diff->getBuildable()) { - return null; - } - $interesting_messages = array(); foreach ($diff->getUnitMessages() as $message) { switch ($message->getResult()) { @@ -1109,6 +1162,7 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setBuildable($diff->getBuildable()) ->setUnitMessages($diff->getUnitMessages()) ->setLimit(5) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setShowViewAll(true); } diff --git a/src/applications/differential/customfield/DifferentialAuthorField.php b/src/applications/differential/customfield/DifferentialAuthorField.php index bac57755ab..fbd74dd2ab 100644 --- a/src/applications/differential/customfield/DifferentialAuthorField.php +++ b/src/applications/differential/customfield/DifferentialAuthorField.php @@ -20,7 +20,7 @@ final class DifferentialAuthorField } public function shouldAppearInPropertyView() { - return true; + return false; } public function renderPropertyViewLabel() { diff --git a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php index d0bd5917dc..6c178f44e0 100644 --- a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php +++ b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php @@ -70,8 +70,6 @@ final class DifferentialHovercardEngineExtension $hovercard->addField(pht('Summary'), $summary); } - $tag = DifferentialRevisionDetailView::renderTagForRevision($revision); - $hovercard->addTag($tag); } } diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php index 2d586e7be4..f32c935e12 100644 --- a/src/applications/differential/view/DifferentialAddCommentView.php +++ b/src/applications/differential/view/DifferentialAddCommentView.php @@ -163,7 +163,7 @@ final class DifferentialAddCommentView extends AphrontView { $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $header_text = $is_serious ? pht('Add Comment') - : pht('Leap Into Action'); + : pht('Leap Into Action!'); $header = id(new PHUIHeaderView()) ->setHeader($header_text); diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 0cd2923018..4f173bda53 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -8,6 +8,7 @@ final class DifferentialChangesetListView extends AphrontView { private $inlineURI; private $renderURI = '/differential/changeset/'; private $whitespace; + private $background; private $standaloneURI; private $leftRawFileURI; @@ -112,6 +113,11 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + public function render() { $viewer = $this->getViewer(); @@ -254,6 +260,7 @@ final class DifferentialChangesetListView extends AphrontView { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setCollapsed(true) + ->setBackground($this->background) ->appendChild($content); return $object_box; diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index ebacabc3f3..639b62fc4b 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -127,6 +127,7 @@ final class DifferentialLocalCommitsView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Local Commits')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/applications/differential/view/DifferentialPrimaryPaneView.php b/src/applications/differential/view/DifferentialPrimaryPaneView.php deleted file mode 100644 index cfa4215fd5..0000000000 --- a/src/applications/differential/view/DifferentialPrimaryPaneView.php +++ /dev/null @@ -1,23 +0,0 @@ -id = $id; - return $this; - } - - public function render() { - - return phutil_tag( - 'div', - array( - 'class' => 'differential-primary-pane', - 'id' => $this->id, - ), - $this->renderChildren()); - } - -} diff --git a/src/applications/differential/view/DifferentialRevisionDetailView.php b/src/applications/differential/view/DifferentialRevisionDetailView.php deleted file mode 100644 index a8436811c2..0000000000 --- a/src/applications/differential/view/DifferentialRevisionDetailView.php +++ /dev/null @@ -1,121 +0,0 @@ -uri = $uri; - return $this; - } - public function getURI() { - return $this->uri; - } - - public function setDiff(DifferentialDiff $diff) { - $this->diff = $diff; - return $this; - } - private function getDiff() { - return $this->diff; - } - - public function setRevision(DifferentialRevision $revision) { - $this->revision = $revision; - return $this; - } - - public function setActions(array $actions) { - $this->actions = $actions; - return $this; - } - private function getActions() { - return $this->actions; - } - - public function setActionList(PhabricatorActionListView $list) { - $this->actionList = $list; - return $this; - } - - public function getActionList() { - return $this->actionList; - } - - public function setCustomFields(PhabricatorCustomFieldList $list) { - $this->customFields = $list; - return $this; - } - - public function render() { - - $this->requireResource('differential-core-view-css'); - - $revision = $this->revision; - $user = $this->getUser(); - - $header = $this->renderHeader($revision); - - $actions = id(new PhabricatorActionListView()) - ->setUser($user) - ->setObject($revision); - foreach ($this->getActions() as $action) { - $actions->addAction($action); - } - - $properties = id(new PHUIPropertyListView()) - ->setUser($user) - ->setObject($revision); - - $properties->setHasKeyboardShortcuts(true); - $properties->setActionList($actions); - $this->setActionList($actions); - - $field_list = $this->customFields; - if ($field_list) { - $field_list->appendFieldsToPropertyList( - $revision, - $user, - $properties); - } - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - - return $object_box; - } - - private function renderHeader(DifferentialRevision $revision) { - $view = id(new PHUIHeaderView()) - ->setHeader($revision->getTitle($revision)) - ->setUser($this->getUser()) - ->setPolicyObject($revision); - - $status = $revision->getStatus(); - $status_name = - DifferentialRevisionStatus::renderFullDescription($status); - - $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); - - return $view; - } - - public static function renderTagForRevision( - DifferentialRevision $revision) { - - $status = $revision->getStatus(); - $status_name = - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); - - return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setName($status_name); - } - -} diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 92394fcb3e..fbafb5a6cc 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -11,6 +11,7 @@ final class DifferentialRevisionListView extends AphrontView { private $header; private $noDataString; private $noBox; + private $background = null; public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; @@ -38,6 +39,11 @@ final class DifferentialRevisionListView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + public function getRequiredHandlePHIDs() { $phids = array(); foreach ($this->revisions as $revision) { @@ -192,6 +198,7 @@ final class DifferentialRevisionListView extends AphrontView { if ($this->header && !$this->noBox) { $list->setFlush(true); $list = id(new PHUIObjectBoxView()) + ->setBackground($this->background) ->setObjectList($list); if ($this->header instanceof PHUIHeaderView) { diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index 082344a492..5ce766afb9 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -305,7 +305,7 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Revision Update History')) - ->setFlush(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($content); } diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 6736b99430..94ca15ac4f 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -214,13 +214,11 @@ final class DiffusionCommitController extends DiffusionController { // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all'); - $change_panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); - $change_panel->setID('toc'); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Changes (%s)', new PhutilNumber($count))); + $warning_view = null; if ($count > self::CHANGES_LIMIT && !$show_all_details) { - $button = id(new PHUIButtonView()) ->setText(pht('Show All Changes')) ->setHref('?show_all=true') @@ -230,11 +228,9 @@ final class DiffusionCommitController extends DiffusionController { $warning_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle(pht('Very Large Commit')) + ->addButton($button) ->appendChild( pht('This commit is very large. Load each file individually.')); - - $change_panel->setInfoView($warning_view); - $header->addActionLink($button); } $changesets = DiffusionPathChange::convertToDifferentialChangesets( @@ -244,12 +240,11 @@ final class DiffusionCommitController extends DiffusionController { // TODO: This table and panel shouldn't really be separate, but we need // to clean up the "Load All Files" interaction first. $change_table = $this->buildTableOfContents( - $changesets); + $changesets, + $header, + $warning_view); - $change_panel->setTable($change_table); - $change_panel->setHeader($header); - - $content[] = $change_panel; + $content[] = $change_table; $vcs = $repository->getVersionControlSystem(); switch ($vcs) { @@ -1017,12 +1012,21 @@ final class DiffusionCommitController extends DiffusionController { return $parser->processCorpus($corpus); } - private function buildTableOfContents(array $changesets) { + private function buildTableOfContents( + array $changesets, + $header, + $info_view) { + $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setHeader($header); + + if ($info_view) { + $toc_view->setInfoView($info_view); + } // TODO: This is hacky, we just want access to the linkX() methods on // DiffusionView. diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index 75de4b20df..49efdc3e25 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -7,6 +7,7 @@ final class HarbormasterUnitSummaryView extends AphrontView { private $limit; private $excuse; private $showViewAll; + private $background; public function setBuildable(HarbormasterBuildable $buildable) { $this->buildable = $buildable; @@ -33,6 +34,11 @@ final class HarbormasterUnitSummaryView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + public function render() { $messages = $this->messages; $buildable = $this->buildable; @@ -54,9 +60,14 @@ final class HarbormasterUnitSummaryView extends AphrontView { $tag_icon = 'fa-ban'; } + $tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($tag_color) + ->setIcon($tag_icon) + ->setName($tag_text); + $header = id(new PHUIHeaderView()) - ->setHeader(pht('Unit Tests')) - ->setStatus($tag_icon, $tag_color, $tag_text); + ->setHeader(array(pht('Unit Tests'), $tag)); if ($this->showViewAll) { $view_all = id(new PHUIButtonView()) @@ -98,6 +109,10 @@ final class HarbormasterUnitSummaryView extends AphrontView { $box->setTable($table); + if ($this->background) { + $box->setBackground($this->background); + } + return $box; } diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index f032e34017..3330620148 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -4,6 +4,9 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { private $items = array(); private $authorityPackages; + private $header; + private $infoView; + private $background; public function addItem(PHUIDiffTableOfContentsItemView $item) { $this->items[] = $item; @@ -20,6 +23,21 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { return $this->authorityPackages; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + + public function setHeader(PHUIHeaderView $header) { + $this->header = $header; + return $this; + } + + public function setInfoView(PHUIInfoView $infoview) { + $this->infoView = $infoview; + return $this; + } + public function render() { $this->requireResource('differential-core-view-css'); $this->requireResource('differential-table-of-contents-css'); @@ -142,11 +160,24 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setAnchorName('toc') ->setNavigationMarker(true); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Table of Contents')) + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Table of Contents')); + + if ($this->header) { + $header = $this->header; + } + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground($this->background) ->setTable($table) ->appendChild($anchor) ->appendChild($buttons); + + if ($this->infoView) { + $box->setInfoView($this->infoView); + } + return $box; } } diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 71cfa3170b..85eb5ab437 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -6,7 +6,7 @@ .differential-changeset { position: relative; margin: 0; - padding-top: 32px; + padding-top: 16px; overflow-x: auto; /* Fixes what seems to be a layout bug in Firefox which causes scrollbars, @@ -265,7 +265,7 @@ td.cov-I { .differential-changeset h1 { font-size: {$biggestfontsize}; - padding: 2px 0 12px 12px; + padding: 2px 0 20px 12px; line-height: 20px; color: #000; } @@ -322,7 +322,7 @@ td.cov-I { .differential-changeset-buttons { float: right; - margin-right: 8px; + margin-right: 12px; } .device-phone .differential-changeset-buttons { @@ -362,3 +362,7 @@ tr.differential-inline-hidden { tr.differential-inline-loading { opacity: 0.5; } + +.differential-review-stage { + position: relative; +} diff --git a/webroot/rsrc/css/application/differential/core.css b/webroot/rsrc/css/application/differential/core.css index 7ccd633f3f..2dcc02bb18 100644 --- a/webroot/rsrc/css/application/differential/core.css +++ b/webroot/rsrc/css/application/differential/core.css @@ -3,7 +3,7 @@ */ .differential-primary-pane { - margin-bottom: 32px; + margin-top: -20px; } .differential-panel { @@ -23,3 +23,7 @@ -ms-user-select: none; user-select: none; } + +.differential-content-hidden { + margin: 0 0 24px 0; +} diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 1d42cde8fa..d59624ab45 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -66,10 +66,6 @@ padding: 0; } -.phui-box.phui-box-blue-property .phui-header-header { - text-transform: uppercase; -} - .phui-box.phui-box-blue-property .phui-header-header .phui-header-icon { margin-right: 6px; } diff --git a/webroot/rsrc/css/phui/phui-object-box.css b/webroot/rsrc/css/phui/phui-object-box.css index f9f06e2d18..dee0525944 100644 --- a/webroot/rsrc/css/phui/phui-object-box.css +++ b/webroot/rsrc/css/phui/phui-object-box.css @@ -52,6 +52,16 @@ div.phui-object-box.phui-object-box-flush { margin: 8px 8px 0 8px; } +.phui-object-box .phui-header-header .phui-tag-view { + margin-left: 8px; +} + +.phui-object-box .phui-header-header .phui-tag-core { + border-color: transparent; + padding: 1px 6px; + font-size: {$normalfontsize}; +} + /* - Object Box Colors ------------------------------------------------------ */ .phui-box-border.phui-object-box-green { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 090fafb63a..a4ee2c4d3f 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -182,12 +182,12 @@ /* Info View */ -.phui-two-column-view .phui-two-column-content .phui-info-view { +.phui-two-column-view .phui-two-column-row .phui-info-view { margin: 0 0 20px 0; padding: 16px; } -.phui-two-column-view .phui-two-column-content .phui-object-box +.phui-two-column-view .phui-two-column-row .phui-object-box .phui-info-view { margin: 0; } From 301ecdef18a34ef4790934c09b76a113d3398e98 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 13 Mar 2016 08:08:45 -0700 Subject: [PATCH 02/25] Convert Drydock to two column layout Summary: Updates Drydock to use two column + curtain layouts. Test Plan: Tested what I could get to, need @epriestley to run this locally for edge cases. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D15467 --- .../DrydockAuthorizationViewController.php | 35 +++++----- .../DrydockBlueprintViewController.php | 68 +++++++++---------- .../drydock/controller/DrydockController.php | 1 + .../controller/DrydockLeaseViewController.php | 48 +++++++------ ...ydockRepositoryOperationViewController.php | 32 +++++---- .../DrydockResourceViewController.php | 50 +++++++------- .../DrydockRepositoryOperationStatusView.php | 1 + 7 files changed, 123 insertions(+), 112 deletions(-) diff --git a/src/applications/drydock/controller/DrydockAuthorizationViewController.php b/src/applications/drydock/controller/DrydockAuthorizationViewController.php index bc34154e4f..80a0e5d1ef 100644 --- a/src/applications/drydock/controller/DrydockAuthorizationViewController.php +++ b/src/applications/drydock/controller/DrydockAuthorizationViewController.php @@ -26,16 +26,14 @@ final class DrydockAuthorizationViewController ->setUser($viewer) ->setPolicyObject($authorization); - $state = $authorization->getBlueprintAuthorizationState(); $icon = DrydockAuthorization::getBlueprintStateIcon($state); $name = DrydockAuthorization::getBlueprintStateName($state); $header->setStatus($icon, null, $name); - $actions = $this->buildActionListView($authorization); + $curtain = $this->buildCurtain($authorization); $properties = $this->buildPropertyListView($authorization); - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -45,29 +43,32 @@ final class DrydockAuthorizationViewController $blueprint->getBlueprintName(), $this->getApplicationURI("blueprint/{$blueprint_id}/")); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockAuthorization $authorization) { + private function buildCurtain(DrydockAuthorization $authorization) { $viewer = $this->getViewer(); $id = $authorization->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($authorization); + $curtain = $this->newCurtainView($authorization); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -84,7 +85,7 @@ final class DrydockAuthorizationViewController $can_authorize = $can_edit && ($state != $state_authorized); $can_decline = $can_edit && ($state != $state_declined); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($authorize_uri) ->setName(pht('Approve Authorization')) @@ -92,7 +93,7 @@ final class DrydockAuthorizationViewController ->setWorkflow(true) ->setDisabled(!$can_authorize)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($decline_uri) ->setName(pht('Decline Authorization')) @@ -100,7 +101,7 @@ final class DrydockAuthorizationViewController ->setWorkflow(true) ->setDisabled(!$can_decline)); - return $view; + return $curtain; } private function buildPropertyListView(DrydockAuthorization $authorization) { diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 21f3a395d3..d3f73bf78c 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -19,7 +19,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($blueprint); + ->setPolicyObject($blueprint) + ->setHeaderIcon('fa-map-o'); if ($blueprint->getIsDisabled()) { $header->setStatus('fa-ban', 'red', pht('Disabled')); @@ -27,15 +28,12 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } - $actions = $this->buildActionListView($blueprint); - $properties = $this->buildPropertyListView($blueprint, $actions); + $curtain = $this->buildCurtain($blueprint); + $properties = $this->buildPropertyListView($blueprint); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $field_list = PhabricatorCustomField::getObjectFields( $blueprint, @@ -49,9 +47,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $viewer, $properties); - $resource_box = $this->buildResourceBox($blueprint); - - $authorizations_box = $this->buildAuthorizationsBox($blueprint); + $resources = $this->buildResourceBox($blueprint); + $authorizations = $this->buildAuthorizationsBox($blueprint); $timeline = $this->buildTransactionTimeline( $blueprint, @@ -61,33 +58,36 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $log_query = id(new DrydockLogQuery()) ->withBlueprintPHIDs(array($blueprint->getPHID())); - $log_box = $this->buildLogBox( + $logs = $this->buildLogBox( $log_query, $this->getApplicationURI("blueprint/{$id}/logs/query/all/")); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $resource_box, - $authorizations_box, - $log_box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties) + ->setMainColumn(array( + $resources, + $authorizations, + $logs, $timeline, - ), - array( - 'title' => $title, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockBlueprint $blueprint) { + private function buildCurtain(DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $id = $blueprint->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($blueprint); - + $curtain = $this->newCurtainView($blueprint); $edit_uri = $this->getApplicationURI("blueprint/edit/{$id}/"); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -95,7 +95,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $blueprint, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($edit_uri) ->setName(pht('Edit Blueprint')) @@ -113,7 +113,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $disable_uri = $this->getApplicationURI("blueprint/{$id}/enable/"); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($disable_uri) ->setName($disable_name) @@ -121,19 +121,15 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { ->setWorkflow(true) ->setDisabled(!$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockBlueprint $blueprint, - PhabricatorActionListView $actions) { + DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($blueprint); - - $view->setActionList($actions); + ->setUser($viewer); $view->addProperty( pht('Type'), @@ -177,6 +173,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return id(new PHUIObjectBoxView()) ->setHeader($resource_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($resource_list); } @@ -242,6 +239,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return id(new PHUIObjectBoxView()) ->setHeader($authorizations_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($authorization_list); } diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 17b8d34916..872a78f66f 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -102,6 +102,7 @@ abstract class DrydockController extends PhabricatorController { ->setText(pht('View All'))); return id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($log_header) ->setTable($log_table); } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 088b197a5f..7166b0bef6 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -21,53 +21,59 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $title = pht('Lease %d', $lease->getID()); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-link'); if ($lease->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } - $actions = $this->buildActionListView($lease); - $properties = $this->buildPropertyListView($lease, $actions); + $curtain = $this->buildCurtain($lease); + $properties = $this->buildPropertyListView($lease); $log_query = id(new DrydockLogQuery()) ->withLeasePHIDs(array($lease->getPHID())); - $log_box = $this->buildLogBox( + $logs = $this->buildLogBox( $log_query, $this->getApplicationURI("lease/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $lease_uri); + $crumbs->setBorder(true); $locks = $this->buildLocksTab($lease->getPHID()); $commands = $this->buildCommandsTab($lease->getPHID()); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, - $log_box, - ), - array( - 'title' => $title, + $logs, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockLease $lease) { + private function buildCurtain(DrydockLease $lease) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($lease); - + $curtain = $this->newCurtainView($lease); $id = $lease->getID(); $can_release = $lease->canRelease(); @@ -80,7 +86,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $lease, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Release Lease')) ->setIcon('fa-times') @@ -88,16 +94,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController { ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockLease $lease, - PhabricatorActionListView $actions) { + DrydockLease $lease) { $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); - $view->setActionList($actions); $view->addProperty( pht('Status'), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php index f8c210368a..e7fbf07b35 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php @@ -25,50 +25,52 @@ final class DrydockRepositoryOperationViewController $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($operation); + ->setPolicyObject($operation) + ->setHeaderIcon('fa-fighter-jet'); $state = $operation->getOperationState(); $icon = DrydockRepositoryOperation::getOperationStateIcon($state); $name = DrydockRepositoryOperation::getOperationStateName($state); $header->setStatus($icon, null, $name); - $actions = $this->buildActionListView($operation); + $curtain = $this->buildCurtain($operation); $properties = $this->buildPropertyListView($operation); - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Operations'), $this->getApplicationURI('operation/')); $crumbs->addTextCrumb($title); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $status_view = id(new DrydockRepositoryOperationStatusView()) ->setUser($viewer) ->setOperation($operation); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties) + ->setMainColumn(array( + $status_view, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $status_view, - )); + $view, + )); } - private function buildActionListView(DrydockRepositoryOperation $operation) { + private function buildCurtain(DrydockRepositoryOperation $operation) { $viewer = $this->getViewer(); $id = $operation->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($operation); + $curtain = $this->newCurtainView($operation); - return $view; + return $curtain; } private function buildPropertyListView( diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 61319fbdbc..c2ab4337f5 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -23,14 +23,15 @@ final class DrydockResourceViewController extends DrydockResourceController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($resource) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-map'); if ($resource->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } - $actions = $this->buildActionListView($resource); - $properties = $this->buildPropertyListView($resource, $actions); + $curtain = $this->buildCurtain($resource); + $properties = $this->buildPropertyListView($resource); $id = $resource->getID(); $resource_uri = $this->getApplicationURI("resource/{$id}/"); @@ -44,37 +45,42 @@ final class DrydockResourceViewController extends DrydockResourceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); + $crumbs->setBorder(true); $locks = $this->buildLocksTab($resource->getPHID()); $commands = $this->buildCommandsTab($resource->getPHID()); + $lease_box = $this->buildLeaseBox($resource); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $lease_box = $this->buildLeaseBox($resource); - - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, $lease_box, $log_box, - ), - array( - 'title' => $title, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockResource $resource) { + private function buildCurtain(DrydockResource $resource) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($resource); + $curtain = $this->newCurtainView($resource); $can_release = $resource->canRelease(); if ($resource->isReleasing()) { @@ -89,7 +95,7 @@ final class DrydockResourceViewController extends DrydockResourceController { $uri = '/resource/'.$resource->getID().'/release/'; $uri = $this->getApplicationURI($uri); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Release Resource')) @@ -97,17 +103,14 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockResource $resource, - PhabricatorActionListView $actions) { + DrydockResource $resource) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setActionList($actions); - + $view = new PHUIPropertyListView(); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); @@ -179,6 +182,7 @@ final class DrydockResourceViewController extends DrydockResourceController { return id(new PHUIObjectBoxView()) ->setHeader($lease_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($lease_list); } diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index d84b4cc0e7..aa7d1e3873 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -48,6 +48,7 @@ final class DrydockRepositoryOperationStatusView $box_view = $this->getBoxView(); if (!$box_view) { $box_view = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeaderText(pht('Operation Status')); } $box_view->setObjectList($list); From d76175285e96252cb3962d6a5a3cde434ed2a80d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 13 Mar 2016 16:01:05 -0700 Subject: [PATCH 03/25] Update Diff view page to new layout Summary: Converts Diff View, single column though. Test Plan: Upload a new diff, review page. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15470 --- .../DifferentialDiffViewController.php | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 716a183b5b..b2c497f6ac 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -126,27 +126,40 @@ final class DifferentialDiffViewController extends DifferentialController { ->setRenderingReferences($refs) ->setStandaloneURI('/differential/changeset/') ->setDiff($diff) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTitle(pht('Diff %d', $diff->getID())) ->setUser($request->getUser()); + $title = pht('Diff %d', $diff->getID()); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Diff %d', $diff->getID())); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); $prop_box = id(new PHUIObjectBoxView()) ->setHeader($property_head) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($property_view) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + + )) + ->setFooter(array( $prop_box, $table_of_contents, $details, - ), - array( - 'title' => pht('Diff View'), )); + + $page = $this->newPage() + ->setTitle(pht('Diff View')) + ->setCrumbs($crumbs) + ->appendChild($view); + return $page; } private function loadSelectableRevisions( From a1dd1ad3cf787d67a626a4a16794fb435e18288b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 14 Mar 2016 18:47:56 -0700 Subject: [PATCH 04/25] Add some padding to object lists in property boxes Summary: These are a little tight in Differential/Audit Test Plan: Review spacing, desktop tablet and mobile. Reviewers: avivey, epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15473 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-box.css | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d05a7b4903..c4e3ae3bf6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'c6ad5231', + 'core.pkg.css' => '225e8ac7', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -123,7 +123,7 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '06153ae3', + 'rsrc/css/phui/phui-box.css' => '96a10c5d', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', @@ -805,7 +805,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '06153ae3', + 'phui-box-css' => '96a10c5d', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index d59624ab45..ea825db3be 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -94,3 +94,7 @@ font-size: 13px; color: {$bluetext}; } + +.phui-box-blue-property .phui-object-item-list-view.phui-object-list-flush { + padding: 2px 8px; +} From 121e68e3adae4cd21731b79c07ca89676def7e19 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 15 Mar 2016 08:16:18 -0700 Subject: [PATCH 05/25] Fix an issue with rendering unit messages for diffs with no buildable Summary: Fixes T10591. This was accidentally reverted in 148a50e48be146b88036032d1cad772d2627da28, probably when resolvign a merge/rebase. Test Plan: Will push to production. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10591 Differential Revision: https://secure.phabricator.com/D15474 --- .../controller/DifferentialRevisionViewController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index c59ee04047..75602af0a6 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1131,6 +1131,10 @@ final class DifferentialRevisionViewController extends DifferentialController { DifferentialRevision $revision) { $viewer = $this->getViewer(); + if (!$diff->getBuildable()) { + return null; + } + if (!$diff->getUnitMessages()) { return null; } From cf15e0de4386416f6565d25fd40e90610184cb29 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 05:17:41 -0700 Subject: [PATCH 06/25] Modularize temporary token types Summary: Ref T10603. For LFS, we need to issue a new type of temporary token. This makes the temporary token code modular so applications can add new token types without modifying the Auth application. (I'm moving slowly here because it impacts authentication.) Test Plan: - Used `bin/auth recover` to get a one-time token from the CLI. - Used "Forgot your password?" to get a one-time token from the web UI. - Followed the web UI token to initiate a password reset, prompting generation of a password token. - Viewed these tokens in the web UI: {F1176908} - Revoked a token. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10603 Differential Revision: https://secure.phabricator.com/D15475 --- src/__phutil_library_map__.php | 6 ++++ .../storage/PhabricatorAuthTemporaryToken.php | 28 +++++++++++-------- ...atorAuthOneTimeLoginTemporaryTokenType.php | 17 +++++++++++ ...torAuthPasswordResetTemporaryTokenType.php | 17 +++++++++++ .../PhabricatorAuthTemporaryTokenType.php | 24 ++++++++++++++++ 5 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php create mode 100644 src/applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php create mode 100644 src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f253b328b2..643467646d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1816,6 +1816,8 @@ phutil_register_library_map(array( 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php', + 'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php', + 'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php', 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php', 'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php', @@ -1844,6 +1846,7 @@ phutil_register_library_map(array( 'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php', 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php', 'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php', + 'PhabricatorAuthTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php', 'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php', 'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', @@ -6123,6 +6126,8 @@ phutil_register_library_map(array( 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController', + 'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', + 'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorAuthProvider' => 'Phobject', 'PhabricatorAuthProviderConfig' => array( 'PhabricatorAuthDAO', @@ -6165,6 +6170,7 @@ phutil_register_library_map(array( ), 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorAuthTemporaryTokenType' => 'Phobject', 'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', diff --git a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php index e379a6aaeb..93e491bdb0 100644 --- a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php +++ b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php @@ -31,14 +31,21 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO ) + parent::getConfiguration(); } + private function newTokenTypeImplementation() { + $types = PhabricatorAuthTemporaryTokenType::getAllTypes(); + + $type = idx($types, $this->tokenType); + if ($type) { + return clone $type; + } + + return null; + } + public function getTokenReadableTypeName() { - // Eventually, it would be nice to let applications implement token types - // so we can put this in modular subclasses. - switch ($this->tokenType) { - case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: - return pht('One-Time Login Token'); - case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: - return pht('Password Reset Token'); + $type = $this->newTokenTypeImplementation(); + if ($type) { + return $type->getTokenReadableTypeName($this); } return $this->tokenType; @@ -49,10 +56,9 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO return false; } - switch ($this->tokenType) { - case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: - case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: - return true; + $type = $this->newTokenTypeImplementation(); + if ($type) { + return $type->isTokenRevocable($this); } return false; diff --git a/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php new file mode 100644 index 0000000000..f48956ff87 --- /dev/null +++ b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php @@ -0,0 +1,17 @@ +getPhobjectClassConstant('TOKENTYPE', 64); + } + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTokenTypeConstant') + ->execute(); + } + +} From 8e3ea4e034eb43178aaac1bcfd836369fb5591af Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 05:32:55 -0700 Subject: [PATCH 07/25] Use new modular temporary auth token constants in one-time login and password reset flows Summary: Ref T10603. This converts existing hard-codes to modular constants. Also removes one small piece of code duplication. Test Plan: - Performed one-time logins. - Performed a password reset. - Verified temporary tokens were revoked properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10603 Differential Revision: https://secure.phabricator.com/D15476 --- .../PhabricatorAuthOneTimeLoginController.php | 21 +++++++------------ .../engine/PhabricatorAuthSessionEngine.php | 17 ++++----------- .../people/editor/PhabricatorUserEditor.php | 6 +++--- .../PhabricatorPasswordSettingsPanel.php | 5 +++-- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index ccc66ffeb7..9ecea74d6a 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -105,23 +105,17 @@ final class PhabricatorAuthOneTimeLoginController // the link in the "Welcome" email is good enough, without requiring users // to go through a second round of email verification. + $editor = id(new PhabricatorUserEditor()) + ->setActor($target_user); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); // Nuke the token and all other outstanding password reset tokens. // There is no particular security benefit to destroying them all, but // it should reduce HackerOne reports of nebulous harm. - - PhabricatorAuthTemporaryToken::revokeTokens( - $target_user, - array($target_user->getPHID()), - array( - PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE, - )); + $editor->revokePasswordResetLinks($target_user); if ($target_email) { - id(new PhabricatorUserEditor()) - ->setActor($target_user) - ->verifyEmail($target_user, $target_email); + $editor->verifyEmail($target_user, $target_email); } unset($unguarded); @@ -133,12 +127,13 @@ final class PhabricatorAuthOneTimeLoginController // We're going to let the user reset their password without knowing // the old one. Generate a one-time token for that. $key = Filesystem::readRandomCharacters(16); + $password_type = + PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) ->setObjectPHID($target_user->getPHID()) - ->setTokenType( - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE) + ->setTokenType($password_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 77ba80bccb..e6289926dd 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -39,17 +39,6 @@ final class PhabricatorAuthSessionEngine extends Phobject { const KIND_UNKNOWN = '?'; - /** - * Temporary tokens for one time logins. - */ - const ONETIME_TEMPORARY_TOKEN_TYPE = 'login:onetime'; - - - /** - * Temporary tokens for password recovery after one time login. - */ - const PASSWORD_TEMPORARY_TOKEN_TYPE = 'login:password'; - const ONETIME_RECOVER = 'recover'; const ONETIME_RESET = 'reset'; const ONETIME_WELCOME = 'welcome'; @@ -642,11 +631,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { $key = Filesystem::readRandomCharacters(32); $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key); + $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) ->setObjectPHID($user->getPHID()) - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenType($onetime_type) ->setTokenExpires(time() + phutil_units('1 day in seconds')) ->setTokenCode($key_hash) ->save(); @@ -685,11 +675,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { $key = null) { $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key); + $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE; return id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withTokenTypes(array($onetime_type)) ->withTokenCodes(array($key_hash)) ->withExpired(false) ->executeOne(); diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index d7885ef101..3370fb428b 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -700,7 +700,7 @@ final class PhabricatorUserEditor extends PhabricatorEditor { } } - private function revokePasswordResetLinks(PhabricatorUser $user) { + public function revokePasswordResetLinks(PhabricatorUser $user) { // Revoke any outstanding password reset links. If an attacker compromises // an account, changes the email address, and sends themselves a password // reset link, it could otherwise remain live for a short period of time @@ -710,8 +710,8 @@ final class PhabricatorUserEditor extends PhabricatorEditor { $user, array($user->getPHID()), array( - PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE, + PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE, + PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE, )); } diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index 56b8fad76a..dd26890fc2 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -40,13 +40,14 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { // the workflow from a password reset email. $key = $request->getStr('key'); + $password_type = PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; + $token = null; if ($key) { $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes( - array(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)) + ->withTokenTypes(array($password_type)) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->withExpired(false) ->executeOne(); From a837c3d73eaf8089265da097eef185a1148005fc Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 05:17:47 -0700 Subject: [PATCH 08/25] Make temporary token storage/schema more flexible Summary: Ref T10603. This makes minor updates to temporary tokens: - Rename `objectPHID` (which is sometimes used to store some other kind of identifier instead of a PHID) to `tokenResource` (i.e., which resource does this token permit access to?). - Add a `userPHID` column. For LFS tokens and some other types of tokens, I want to bind the token to both a resource (like a repository) and a user. - Add a `properties` column. This makes tokens more flexible and supports custom behavior (like scoping LFS tokens even more tightly). Test Plan: - Ran `bin/storage upgrade -f`, got a clean upgrade. - Viewed one-time tokens. - Revoked one token. - Revoked all tokens. - Performed a one-time login. - Performed a password reset. - Added an MFA token. - Removed an MFA token. - Used a file token to view a file. - Verified file token was removed after viewing file. - Linked my account to an OAuth1 account (Twitter). Reviewers: chad Reviewed By: chad Maniphest Tasks: T10603 Differential Revision: https://secure.phabricator.com/D15478 --- .../20160316.lfs.01.token.resource.sql | 2 + .../20160316.lfs.02.token.user.sql | 2 + .../20160316.lfs.03.token.properties.sql | 2 + .../20160316.lfs.04.token.default.sql | 2 + .../PhabricatorAuthOneTimeLoginController.php | 2 +- .../PhabricatorAuthRevokeTokenController.php | 2 +- .../engine/PhabricatorAuthSessionEngine.php | 4 +- .../auth/factor/PhabricatorTOTPAuthFactor.php | 4 +- .../PhabricatorOAuth1AuthProvider.php | 6 +- .../PhabricatorAuthTemporaryTokenQuery.php | 62 ++++++++++--------- .../storage/PhabricatorAuthTemporaryToken.php | 32 +++++++--- .../files/storage/PhabricatorFile.php | 4 +- .../PhabricatorPasswordSettingsPanel.php | 2 +- .../panel/PhabricatorTokensSettingsPanel.php | 2 +- 14 files changed, 79 insertions(+), 49 deletions(-) create mode 100644 resources/sql/autopatches/20160316.lfs.01.token.resource.sql create mode 100644 resources/sql/autopatches/20160316.lfs.02.token.user.sql create mode 100644 resources/sql/autopatches/20160316.lfs.03.token.properties.sql create mode 100644 resources/sql/autopatches/20160316.lfs.04.token.default.sql diff --git a/resources/sql/autopatches/20160316.lfs.01.token.resource.sql b/resources/sql/autopatches/20160316.lfs.01.token.resource.sql new file mode 100644 index 0000000000..7be5bbda54 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.01.token.resource.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + CHANGE objectPHID tokenResource VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20160316.lfs.02.token.user.sql b/resources/sql/autopatches/20160316.lfs.02.token.user.sql new file mode 100644 index 0000000000..72174d6fe8 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.02.token.user.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + ADD userPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160316.lfs.03.token.properties.sql b/resources/sql/autopatches/20160316.lfs.03.token.properties.sql new file mode 100644 index 0000000000..2cb4449d73 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.03.token.properties.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160316.lfs.04.token.default.sql b/resources/sql/autopatches/20160316.lfs.04.token.default.sql new file mode 100644 index 0000000000..0f0ce4abc4 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.04.token.default.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_auth.auth_temporarytoken + SET properties = '{}' WHERE properties = ''; diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index 9ecea74d6a..d98879d0ed 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -132,7 +132,7 @@ final class PhabricatorAuthOneTimeLoginController $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($target_user->getPHID()) + ->setTokenResource($target_user->getPHID()) ->setTokenType($password_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) diff --git a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php index c1f0c21cb1..6d516916eb 100644 --- a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php +++ b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php @@ -11,7 +11,7 @@ final class PhabricatorAuthRevokeTokenController $query = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs(array($viewer->getPHID())); + ->withTokenResources(array($viewer->getPHID())); if (!$is_all) { $query->withIDs(array($id)); } diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index e6289926dd..98b6a63b5a 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -635,7 +635,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($user->getPHID()) + ->setTokenResource($user->getPHID()) ->setTokenType($onetime_type) ->setTokenExpires(time() + phutil_units('1 day in seconds')) ->setTokenCode($key_hash) @@ -679,7 +679,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { return id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) + ->withTokenResources(array($user->getPHID())) ->withTokenTypes(array($onetime_type)) ->withTokenCodes(array($key_hash)) ->withExpired(false) diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php index 8c29eb7d14..418270de63 100644 --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -36,7 +36,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) + ->withTokenResources(array($user->getPHID())) ->withTokenTypes(array(self::TEMPORARY_TOKEN_TYPE)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($key))) @@ -55,7 +55,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($user->getPHID()) + ->setTokenResource($user->getPHID()) ->setTokenType(self::TEMPORARY_TOKEN_TYPE) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) diff --git a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php index a22707b6cc..bc9572061f 100644 --- a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php +++ b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php @@ -221,7 +221,7 @@ abstract class PhabricatorOAuth1AuthProvider // Wipe out an existing token, if one exists. $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($key)) + ->withTokenResources(array($key)) ->withTokenTypes(array($type)) ->executeOne(); if ($token) { @@ -230,7 +230,7 @@ abstract class PhabricatorOAuth1AuthProvider // Save the new secret. id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($key) + ->setTokenResource($key) ->setTokenType($type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode($secret) @@ -243,7 +243,7 @@ abstract class PhabricatorOAuth1AuthProvider $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($key)) + ->withTokenResources(array($key)) ->withTokenTypes(array($type)) ->withExpired(false) ->executeOne(); diff --git a/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php b/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php index aecd92a4a7..72141f75f0 100644 --- a/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php +++ b/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php @@ -4,8 +4,9 @@ final class PhabricatorAuthTemporaryTokenQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $ids; - private $objectPHIDs; + private $tokenResources; private $tokenTypes; + private $userPHIDs; private $expired; private $tokenCodes; @@ -14,8 +15,8 @@ final class PhabricatorAuthTemporaryTokenQuery return $this; } - public function withObjectPHIDs(array $object_phids) { - $this->objectPHIDs = $object_phids; + public function withTokenResources(array $resources) { + $this->tokenResources = $resources; return $this; } @@ -34,41 +35,39 @@ final class PhabricatorAuthTemporaryTokenQuery return $this; } - protected function loadPage() { - $table = new PhabricatorAuthTemporaryToken(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function withUserPHIDs(array $phids) { + $this->userPHIDs = $phids; + return $this; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + public function newResultObject() { + return new PhabricatorAuthTemporaryToken(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->objectPHIDs !== null) { + if ($this->tokenResources !== null) { $where[] = qsprintf( - $conn_r, - 'objectPHID IN (%Ls)', - $this->objectPHIDs); + $conn, + 'tokenResource IN (%Ls)', + $this->tokenResources); } if ($this->tokenTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenType IN (%Ls)', $this->tokenTypes); } @@ -76,12 +75,12 @@ final class PhabricatorAuthTemporaryTokenQuery if ($this->expired !== null) { if ($this->expired) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenExpires <= %d', time()); } else { $where[] = qsprintf( - $conn_r, + $conn, 'tokenExpires > %d', time()); } @@ -89,14 +88,19 @@ final class PhabricatorAuthTemporaryTokenQuery if ($this->tokenCodes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenCode IN (%Ls)', $this->tokenCodes); } - $where[] = $this->buildPagingClause($conn_r); + if ($this->userPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'userPHID IN (%Ls)', + $this->userPHIDs); + } - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php index 93e491bdb0..be08f5db2d 100644 --- a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php +++ b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php @@ -3,30 +3,39 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO implements PhabricatorPolicyInterface { - // TODO: OAuth1 stores a client identifier here, which is not a real PHID. - // At some point, we should rename this column to be a little more generic. - protected $objectPHID; - + // NOTE: This is usually a PHID, but may be some other kind of resource + // identifier for some token types. + protected $tokenResource; protected $tokenType; protected $tokenExpires; protected $tokenCode; + protected $userPHID; + protected $properties; protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( + 'tokenResource' => 'phid', 'tokenType' => 'text64', 'tokenExpires' => 'epoch', 'tokenCode' => 'text64', + 'userPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_token' => array( - 'columns' => array('objectPHID', 'tokenType', 'tokenCode'), + 'columns' => array('tokenResource', 'tokenType', 'tokenCode'), 'unique' => true, ), 'key_expires' => array( 'columns' => array('tokenExpires'), ), + 'key_user' => array( + 'columns' => array('userPHID'), + ), ), ) + parent::getConfiguration(); } @@ -73,12 +82,12 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO public static function revokeTokens( PhabricatorUser $viewer, - array $object_phids, + array $token_resources, array $token_types) { $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs($object_phids) + ->withTokenResources($token_resources) ->withTokenTypes($token_types) ->withExpired(false) ->execute(); @@ -88,6 +97,15 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO } } + public function getTemporaryTokenProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setTemporaryTokenProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 28328796ce..58cfe346c4 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -1123,7 +1123,7 @@ final class PhabricatorFile extends PhabricatorFileDAO // Save the new secret. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $token = id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($this->getPHID()) + ->setTokenResource($this->getPHID()) ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) @@ -1136,7 +1136,7 @@ final class PhabricatorFile extends PhabricatorFileDAO public function validateOneTimeToken($token_code) { $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($this->getPHID())) + ->withTokenResources(array($this->getPHID())) ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($token_code))) diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index dd26890fc2..f2db373945 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -46,7 +46,7 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { if ($key) { $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) + ->withTokenResources(array($user->getPHID())) ->withTokenTypes(array($password_type)) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->withExpired(false) diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php index a92026333a..030ad37116 100644 --- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php @@ -23,7 +23,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs(array($viewer->getPHID())) + ->withTokenResources(array($viewer->getPHID())) ->execute(); $rows = array(); From 33a95d44bdc76a9d0f72a6d7489c1f33a04b68a7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 06:17:31 -0700 Subject: [PATCH 09/25] Formally modularize MFA/TOTP tokens, provide a module panel for temporary tokens Summary: Ref T10603. We have a couple of sort of ad-hoc tokens, so start formalizing them. First up is MFA tokens. Also adds a new config module panel for these. Test Plan: - Added MFA. - Added MFA, intentionally fumbled the input, completed the workflow. - Removed MFA. - Viewed tokens, saw MFA sync tokens. - Viewed new module config panel. {F1177014} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10603 Differential Revision: https://secure.phabricator.com/D15479 --- src/__phutil_library_map__.php | 4 ++ ...abricatorAuthTOTPKeyTemporaryTokenType.php | 17 +++++++ .../auth/factor/PhabricatorTOTPAuthFactor.php | 8 ++-- ...atorAuthOneTimeLoginTemporaryTokenType.php | 4 ++ ...torAuthPasswordResetTemporaryTokenType.php | 4 ++ .../PhabricatorAuthTemporaryTokenType.php | 1 + ...habricatorAuthTemporaryTokenTypeModule.php | 47 +++++++++++++++++++ 7 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php create mode 100644 src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 643467646d..efdb6a4eb6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1843,10 +1843,12 @@ phutil_register_library_map(array( 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', + 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php', 'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php', 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php', 'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php', 'PhabricatorAuthTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php', + 'PhabricatorAuthTemporaryTokenTypeModule' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php', 'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php', 'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', @@ -6164,6 +6166,7 @@ phutil_register_library_map(array( 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', + 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorAuthTemporaryToken' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', @@ -6171,6 +6174,7 @@ phutil_register_library_map(array( 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthTemporaryTokenType' => 'Phobject', + 'PhabricatorAuthTemporaryTokenTypeModule' => 'PhabricatorConfigModule', 'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', diff --git a/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php b/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php new file mode 100644 index 0000000000..02f62e76be --- /dev/null +++ b/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php @@ -0,0 +1,17 @@ +getStr('totpkey'); if (strlen($key)) { // If the user is providing a key, make sure it's a key we generated. @@ -37,7 +37,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) ->withTokenResources(array($user->getPHID())) - ->withTokenTypes(array(self::TEMPORARY_TOKEN_TYPE)) + ->withTokenTypes(array($totp_token_type)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->executeOne(); @@ -56,7 +56,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) ->setTokenResource($user->getPHID()) - ->setTokenType(self::TEMPORARY_TOKEN_TYPE) + ->setTokenType($totp_token_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); diff --git a/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php index f48956ff87..b5b0b35271 100644 --- a/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php +++ b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php @@ -5,6 +5,10 @@ final class PhabricatorAuthOneTimeLoginTemporaryTokenType const TOKENTYPE = 'login:onetime'; + public function getTokenTypeDisplayName() { + return pht('One-Time Login'); + } + public function getTokenReadableTypeName( PhabricatorAuthTemporaryToken $token) { return pht('One-Time Login Token'); diff --git a/src/applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php b/src/applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php index bd82bca596..d6af644e17 100644 --- a/src/applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php +++ b/src/applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php @@ -5,6 +5,10 @@ final class PhabricatorAuthPasswordResetTemporaryTokenType const TOKENTYPE = 'login:password'; + public function getTokenTypeDisplayName() { + return pht('Password Reset'); + } + public function getTokenReadableTypeName( PhabricatorAuthTemporaryToken $token) { return pht('Password Reset Token'); diff --git a/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php index aaad89504c..842afd720e 100644 --- a/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php +++ b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php @@ -3,6 +3,7 @@ abstract class PhabricatorAuthTemporaryTokenType extends Phobject { + abstract public function getTokenTypeDisplayName(); abstract public function getTokenReadableTypeName( PhabricatorAuthTemporaryToken $token); diff --git a/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php new file mode 100644 index 0000000000..8f4ad9ea9b --- /dev/null +++ b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php @@ -0,0 +1,47 @@ +getViewer(); + + $types = PhabricatorAuthTemporaryTokenType::getAllTypes(); + + $rows = array(); + foreach ($types as $type) { + $rows[] = array( + get_class($type), + $type->getTokenTypeConstant(), + $type->getTokenTypeDisplayName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Class'), + pht('Key'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Temporary Token Types')) + ->setTable($table); + } + +} From 6ef4747e9da5d2048ebd180d63cc97349523ec93 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 06:36:04 -0700 Subject: [PATCH 10/25] Convert OAuth1 handshake tokens to new modular temporary tokens Summary: Ref T10603. Swap these over and give them nice UI strings. Test Plan: - Refreshed a Twitter OAuth link. - Unlinked and re-linked a Twitter account. - Viewed the new type in {nav Config > Temporary Tokens}. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10603 Differential Revision: https://secure.phabricator.com/D15480 --- src/__phutil_library_map__.php | 2 ++ .../provider/PhabricatorOAuth1AuthProvider.php | 11 +++++++---- ...habricatorOAuth1SecretTemporaryTokenType.php | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index efdb6a4eb6..aa0202b656 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2672,6 +2672,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', 'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php', + 'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php', 'PhabricatorOAuth2AuthProvider' => 'applications/auth/provider/PhabricatorOAuth2AuthProvider.php', 'PhabricatorOAuthAuthProvider' => 'applications/auth/provider/PhabricatorOAuthAuthProvider.php', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php', @@ -7122,6 +7123,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', 'PhabricatorNuanceApplication' => 'PhabricatorApplication', 'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider', + 'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorOAuth2AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuthAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorOAuthClientAuthorization' => array( diff --git a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php index bc9572061f..530bf30583 100644 --- a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php +++ b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php @@ -9,8 +9,6 @@ abstract class PhabricatorOAuth1AuthProvider const PROPERTY_CONSUMER_SECRET = 'oauth1:consumer:secret'; const PROPERTY_PRIVATE_KEY = 'oauth1:private:key'; - const TEMPORARY_TOKEN_TYPE = 'oauth1:request:secret'; - protected function getIDKey() { return self::PROPERTY_CONSUMER_KEY; } @@ -215,8 +213,9 @@ abstract class PhabricatorOAuth1AuthProvider private function saveHandshakeTokenSecret($client_code, $secret) { + $secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE; $key = $this->getHandshakeTokenKeyFromClientCode($client_code); - $type = $this->getTemporaryTokenType(self::TEMPORARY_TOKEN_TYPE); + $type = $this->getTemporaryTokenType($secret_type); // Wipe out an existing token, if one exists. $token = id(new PhabricatorAuthTemporaryTokenQuery()) @@ -238,8 +237,9 @@ abstract class PhabricatorOAuth1AuthProvider } private function loadHandshakeTokenSecret($client_code) { + $secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE; $key = $this->getHandshakeTokenKeyFromClientCode($client_code); - $type = $this->getTemporaryTokenType(self::TEMPORARY_TOKEN_TYPE); + $type = $this->getTemporaryTokenType($secret_type); $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -263,6 +263,9 @@ abstract class PhabricatorOAuth1AuthProvider // others' toes if a user starts Mediawiki and Bitbucket auth at the // same time. + // TODO: This isn't really a proper use of the table and should get + // cleaned up some day: the type should be constant. + return $core_type.':'.$this->getProviderConfig()->getID(); } diff --git a/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php b/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php new file mode 100644 index 0000000000..b104427bfa --- /dev/null +++ b/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php @@ -0,0 +1,17 @@ + Date: Wed, 16 Mar 2016 07:10:06 -0700 Subject: [PATCH 11/25] Convert one-time file access tokens to modular token types Summary: Fixes T10603. This is the last of the ad-hoc temporary tokens. Test Plan: - Used a file token. - Viewed type in {nav Config > Temporary Tokens}. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10603 Differential Revision: https://secure.phabricator.com/D15481 --- src/__phutil_library_map__.php | 2 ++ .../files/storage/PhabricatorFile.php | 8 +++++--- .../PhabricatorFileAccessTemporaryTokenType.php | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index aa0202b656..e2eb04052b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2352,6 +2352,7 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', + 'PhabricatorFileAccessTemporaryTokenType' => 'applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', 'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php', @@ -6770,6 +6771,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorFileAccessTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileChunk' => array( 'PhabricatorFileDAO', diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 58cfe346c4..f1d4c11bd7 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -26,7 +26,6 @@ final class PhabricatorFile extends PhabricatorFileDAO PhabricatorPolicyInterface, PhabricatorDestructibleInterface { - const ONETIME_TEMPORARY_TOKEN_TYPE = 'file:onetime'; const STORAGE_FORMAT_RAW = 'raw'; const METADATA_IMAGE_WIDTH = 'width'; @@ -1119,12 +1118,13 @@ final class PhabricatorFile extends PhabricatorFileDAO protected function generateOneTimeToken() { $key = Filesystem::readRandomCharacters(16); + $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; // Save the new secret. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $token = id(new PhabricatorAuthTemporaryToken()) ->setTokenResource($this->getPHID()) - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenType($token_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); @@ -1134,10 +1134,12 @@ final class PhabricatorFile extends PhabricatorFileDAO } public function validateOneTimeToken($token_code) { + $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; + $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withTokenResources(array($this->getPHID())) - ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withTokenTypes(array($token_type)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($token_code))) ->executeOne(); diff --git a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php new file mode 100644 index 0000000000..73b5cd8c09 --- /dev/null +++ b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php @@ -0,0 +1,17 @@ + Date: Wed, 16 Mar 2016 14:48:51 -0700 Subject: [PATCH 12/25] Remove recently added repository_pathchange key Summary: Ref T10560. Reverts D15460. See that task for discussion: we dug up some more information to explain the behavior, and this key was just sort of sidestepping an analyze/cardinality estimate issue on the index. With proper cardinality estimates it shouldn't be used, so just nuke it. Test Plan: Ran `bin/storage adjust`, saw key drop. Reviewers: eadler, chad Reviewed By: chad Maniphest Tasks: T10560 Differential Revision: https://secure.phabricator.com/D15486 --- .../repository/storage/PhabricatorRepositorySchemaSpec.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php b/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php index 4471151d4e..8ff2ed0c9b 100644 --- a/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php +++ b/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php @@ -155,9 +155,6 @@ final class PhabricatorRepositorySchemaSpec 'repositoryID' => array( 'columns' => array('repositoryID', 'pathID', 'commitSequence'), ), - 'key_history' => array( - 'columns' => array('commitID', 'isDirect', 'changeType'), - ), )); $this->buildRawSchema( From 51153a580cd60e7b93dbcf43c85a963eec82aa1a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 07:44:25 -0700 Subject: [PATCH 13/25] Implement "git-lfs-authenticate" over SSH Summary: Ref T7789. This implements a (probably) usable "git-lfs-authenticate" on top of the new temporary token infrastructure. This won't actually do anything yet, since nothing reads the tokens. Test Plan: ``` $ ./bin/ssh-exec --phabricator-ssh-user admin --ssh-command 'git-lfs-authenticate' phabricator-ssh-exec: Expected `git-lfs-authenticate `, but received too few arguments. ``` ``` $ ./bin/ssh-exec --phabricator-ssh-user admin --ssh-command 'git-lfs-authenticate x' phabricator-ssh-exec: Unrecognized repository path "x". Expected a path like "/diffusion/X/" or "/diffusion/123/". ``` ``` $ ./bin/ssh-exec --phabricator-ssh-user admin --ssh-command 'git-lfs-authenticate diffusion/22' Exception: Expected `git-lfs-authenticate `, but received too few arguments. ``` ``` $ ./bin/ssh-exec --phabricator-ssh-user admin --ssh-command 'git-lfs-authenticate diffusion/22 y' Exception: Git LFS operation "y" is not supported by this server. ``` ``` $ ./bin/ssh-exec --phabricator-ssh-user admin --ssh-command 'git-lfs-authenticate diffusion/22 upload' {"header":{"Authorization":"Basic QGdpdC1sZnM6NmR2bDVreWVsaXNuMmtnNXBtbnZwM3VlaWhubmI1bmI="},"href":"http:\/\/local.phacility.com\/diffusion\/22\/new-callsign-free-repository.git\/info\/lfs"} ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D15482 --- src/__phutil_library_map__.php | 4 + .../DiffusionGitLFSAuthenticateWorkflow.php | 118 ++++++++++++++++++ .../DiffusionGitLFSTemporaryTokenType.php | 18 +++ .../storage/PhabricatorRepository.php | 36 ++++++ 4 files changed, 176 insertions(+) create mode 100644 src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php create mode 100644 src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e2eb04052b..6043a8d5fa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -633,6 +633,8 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', + 'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php', + 'DiffusionGitLFSTemporaryTokenType' => 'applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', @@ -4760,6 +4762,8 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', + 'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGitLFSTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionGitRequest' => 'DiffusionRequest', diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php new file mode 100644 index 0000000000..aa46a9e2c2 --- /dev/null +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php @@ -0,0 +1,118 @@ +setName('git-lfs-authenticate'); + $this->setArguments( + array( + array( + 'name' => 'argv', + 'wildcard' => true, + ), + )); + } + + protected function identifyRepository() { + return $this->loadRepositoryWithPath($this->getLFSPathArgument()); + } + + private function getLFSPathArgument() { + return $this->getLFSArgument(0); + } + + private function getLFSOperationArgument() { + return $this->getLFSArgument(1); + } + + private function getLFSArgument($position) { + $args = $this->getArgs(); + $argv = $args->getArg('argv'); + + if (!isset($argv[$position])) { + throw new Exception( + pht( + 'Expected `git-lfs-authenticate `, but received '. + 'too few arguments.')); + } + + return $argv[$position]; + } + + protected function executeRepositoryOperations() { + $operation = $this->getLFSOperationArgument(); + + // NOTE: We aren't checking write access here, even for "upload". The + // HTTP endpoint should be able to do that for us. + + switch ($operation) { + case 'upload': + case 'download': + break; + default: + throw new Exception( + pht( + 'Git LFS operation "%s" is not supported by this server.', + $operation)); + } + + $repository = $this->getRepository(); + + if (!$repository->isGit()) { + throw new Exception( + pht( + 'Repository "%s" is not a Git repository. Git LFS is only '. + 'supported for Git repositories.', + $repository->getDisplayName())); + } + + if (!$repository->canUseGitLFS()) { + throw new Exception( + pht('Git LFS is not enabled for this repository.')); + } + + // NOTE: This is usually the same as the default URI (which does not + // need to be specified in the response), but the protocol or domain may + // differ in some situations. + + $lfs_uri = $repository->getGitLFSURI('info/lfs'); + + // Generate a temporary token to allow the user to acces LFS over HTTP. + // This works even if normal HTTP repository operations are not available + // on this host, and does not require the user to have a VCS password. + + $user = $this->getUser(); + $headers = array(); + + $lfs_user = DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME; + $lfs_pass = Filesystem::readRandomCharacters(32); + $lfs_hash = PhabricatorHash::digest($lfs_pass); + + $ttl = PhabricatorTime::getNow() + phutil_units('1 day in seconds'); + + $token = id(new PhabricatorAuthTemporaryToken()) + ->setTokenResource($repository->getPHID()) + ->setTokenType(DiffusionGitLFSTemporaryTokenType::TOKENTYPE) + ->setTokenCode($lfs_hash) + ->setUserPHID($user->getPHID()) + ->setTemporaryTokenProperty('lfs.operation', $operation) + ->setTokenExpires($ttl) + ->save(); + + $authorization_header = base64_encode($lfs_user.':'.$lfs_pass); + $headers['Authorization'] = 'Basic '.$authorization_header; + + $result = array( + 'header' => $headers, + 'href' => $lfs_uri, + ); + $result = phutil_json_encode($result); + + $this->writeIO($result); + $this->waitForGitClient(); + + return 0; + } + +} diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php new file mode 100644 index 0000000000..f7cf558c36 --- /dev/null +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php @@ -0,0 +1,18 @@ +getRawHTTPCloneURIObject(); + } + + private function getRawHTTPCloneURIObject() { $uri = PhabricatorEnv::getProductionURI($this->getURI()); $uri = new PhutilURI($uri); @@ -1819,6 +1823,38 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return !$this->isSVN(); } + public function canUseGitLFS() { + if (!$this->isGit()) { + return false; + } + + if (!$this->isHosted()) { + return false; + } + + // TODO: Unprototype this feature. + if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + return false; + } + + return true; + } + + public function getGitLFSURI($path = null) { + if (!$this->canUseGitLFS()) { + throw new Exception( + pht( + 'This repository does not support Git LFS, so Git LFS URIs can '. + 'not be generated for it.')); + } + + $uri = $this->getRawHTTPCloneURIObject(); + $uri = (string)$uri; + $uri = $uri.'/'.$path; + + return $uri; + } + public function canMirror() { if ($this->isGit() || $this->isHg()) { return true; From 2b02024e2329f9d21590c90452c0b0adf6509d2f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 10:47:07 -0700 Subject: [PATCH 14/25] Use AphrontRequestStream to read request input Summary: Ref T10604. This uses the new standalone stream reader introduced in D15483 to read request data, instead of putting the logic in PhabricatorStartup. It also doesn't read request data until it specifically needs to. This supports, e.g., streaming Git LFS PUT requests, and streaming more types of requests in the future. Test Plan: See D15483. Made various different types of requests and wasn't immediately able to break anything. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10604 Differential Revision: https://secure.phabricator.com/D15484 --- ...AphrontDefaultApplicationConfiguration.php | 17 ++++-- support/PhabricatorStartup.php | 61 ++++++------------- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index cd1347411a..be23895bec 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -25,11 +25,18 @@ class AphrontDefaultApplicationConfiguration $content_type = idx($_SERVER, 'CONTENT_TYPE'); $is_form_data = preg_match('@^multipart/form-data@i', $content_type); - $raw_input = PhabricatorStartup::getRawInput(); - if (strlen($raw_input) && !$is_form_data) { - $data += $parser->parseQueryString($raw_input); - } else if ($_POST) { - $data += $_POST; + $request_method = idx($_SERVER, 'REQUEST_METHOD'); + if ($request_method === 'PUT') { + // For PUT requests, do nothing: in particular, do NOT read input. This + // allows us to stream input later and process very large PUT requests, + // like those coming from Git LFS. + } else { + $raw_input = PhabricatorStartup::getRawInput(); + if (strlen($raw_input) && !$is_form_data) { + $data += $parser->parseQueryString($raw_input); + } else if ($_POST) { + $data += $_POST; + } } $data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', '')); diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 5174767e28..6c50ed603e 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -83,6 +83,26 @@ final class PhabricatorStartup { * @task info */ public static function getRawInput() { + if (self::$rawInput === null) { + $stream = new AphrontRequestStream(); + + if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { + $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); + $stream->setEncoding($encoding); + } + + $input = ''; + do { + $bytes = $stream->readData(); + if ($bytes === null) { + break; + } + $input .= $bytes; + } while (true); + + self::$rawInput = $input; + } + return self::$rawInput; } @@ -128,47 +148,6 @@ final class PhabricatorStartup { self::detectPostMaxSizeTriggered(); self::beginOutputCapture(); - - if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { - $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); - } else { - $encoding = null; - } - - $input_stream = fopen('php://input', 'rb'); - if (!$input_stream) { - self::didFatal( - 'Unable to open "php://input" to read HTTP request body.'); - } - - if ($encoding === 'gzip') { - $ok = stream_filter_append( - $input_stream, - 'zlib.inflate', - STREAM_FILTER_READ, - array( - 'window' => 30, - )); - - if (!$ok) { - self::didFatal( - 'Failed to append gzip inflate filter to HTTP request body input '. - 'stream.'); - } - } - - $input_data = ''; - while (!feof($input_stream)) { - $read_bytes = fread($input_stream, 16 * 1024); - if ($read_bytes === false) { - self::didFatal( - 'Failed to read input bytes from HTTP request body.'); - } - $input_data .= $read_bytes; - } - fclose($input_stream); - - self::$rawInput = $input_data; } From 08b1a33dc36a7bbe429de803f91ecd27511f3bbe Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Mar 2016 12:42:21 -0700 Subject: [PATCH 15/25] Implement a Git LFS server which supports no operations Summary: Ref T7789. This builds on top of `git-lfs-authenticate` to detect LFS requests, read LFS tokens, and route them to a handler which can do useful things. This handler promptly drops them on the floor with an error message. Test Plan: Here's a transcript showing the parts working together so far: - `git-lfs` connects to the server with SSH, and gets told how to connect with HTTP to do uploads. - `git-lfs` uses HTTP, and authenticates with the tokens properly. - But the server tells it to go away, and that it doesn't support anything, so the operation ultimately fails. ``` $ GIT_TRACE=1 git lfs push origin master 12:45:56.153913 git.c:558 trace: exec: 'git-lfs' 'push' 'origin' 'master' 12:45:56.154376 run-command.c:335 trace: run_command: 'git-lfs' 'push' 'origin' 'master' trace git-lfs: Upload refs origin to remote [master] trace git-lfs: run_command: git rev-list --objects master --not --remotes=origin trace git-lfs: run_command: git cat-file --batch-check trace git-lfs: run_command: git cat-file --batch trace git-lfs: run_command: 'git' config -l trace git-lfs: tq: starting 3 transfer workers trace git-lfs: tq: running as batched queue, batch size of 100 trace git-lfs: prepare upload: b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69 lfs/dog1.jpg 1/1 trace git-lfs: tq: sending batch of size 1 trace git-lfs: ssh: local@localvault.phacility.com git-lfs-authenticate diffusion/18/poems.git upload trace git-lfs: api: batch 1 files trace git-lfs: HTTP: POST http://local.phacility.com/diffusion/POEMS/poems.git/info/lfs/objects/batch trace git-lfs: HTTP: 404 trace git-lfs: HTTP: {"message":"Git LFS operation \"objects\/batch\" is not supported by this server."} trace git-lfs: HTTP: trace git-lfs: api: batch not implemented: 404 trace git-lfs: run_command: 'git' config lfs.batch false trace git-lfs: tq: batch api not implemented, falling back to individual trace git-lfs: ssh: local@localvault.phacility.com git-lfs-authenticate diffusion/18/poems.git upload b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69 trace git-lfs: api: uploading (b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69) trace git-lfs: HTTP: POST http://local.phacility.com/diffusion/POEMS/poems.git/info/lfs/objects trace git-lfs: HTTP: 404 trace git-lfs: HTTP: {"message":"Git LFS operation \"objects\" is not supported by this server."} trace git-lfs: HTTP: trace git-lfs: tq: retrying 1 failed transfers trace git-lfs: ssh: local@localvault.phacility.com git-lfs-authenticate diffusion/18/poems.git upload b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69 trace git-lfs: api: uploading (b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69) trace git-lfs: HTTP: POST http://local.phacility.com/diffusion/POEMS/poems.git/info/lfs/objects trace git-lfs: HTTP: 404 trace git-lfs: HTTP: {"message":"Git LFS operation \"objects\" is not supported by this server."} trace git-lfs: HTTP: Git LFS: (0 of 1 files) 0 B / 87.12 KB Git LFS operation "objects" is not supported by this server. Git LFS operation "objects" is not supported by this server. ``` Reviewers: chad Reviewed By: chad Subscribers: eadler Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D15485 --- src/__phutil_library_map__.php | 2 + .../controller/DiffusionServeController.php | 238 +++++++++++++++--- .../response/DiffusionGitLFSResponse.php | 37 +++ 3 files changed, 240 insertions(+), 37 deletions(-) create mode 100644 src/applications/diffusion/response/DiffusionGitLFSResponse.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6043a8d5fa..85400a5ebc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -634,6 +634,7 @@ phutil_register_library_map(array( 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', 'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php', + 'DiffusionGitLFSResponse' => 'applications/diffusion/response/DiffusionGitLFSResponse.php', 'DiffusionGitLFSTemporaryTokenType' => 'applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', @@ -4763,6 +4764,7 @@ phutil_register_library_map(array( 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGitLFSResponse' => 'AphrontResponse', 'DiffusionGitLFSTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 84010d76af..fcc260b287 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -5,6 +5,9 @@ final class DiffusionServeController extends DiffusionController { private $serviceViewer; private $serviceRepository; + private $isGitLFSRequest; + private $gitLFSToken; + public function setServiceViewer(PhabricatorUser $viewer) { $this->serviceViewer = $viewer; return $this; @@ -23,6 +26,14 @@ final class DiffusionServeController extends DiffusionController { return $this->serviceRepository; } + public function getIsGitLFSRequest() { + return $this->isGitLFSRequest; + } + + public function getGitLFSToken() { + return $this->gitLFSToken; + } + public function isVCSRequest(AphrontRequest $request) { $identifier = $this->getRepositoryIdentifierFromRequest($request); if ($identifier === null) { @@ -32,6 +43,9 @@ final class DiffusionServeController extends DiffusionController { $content_type = $request->getHTTPHeader('Content-Type'); $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); + // This may have a "charset" suffix, so only match the prefix. + $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))'; + $vcs = null; if ($request->getExists('service')) { $service = $request->getStr('service'); @@ -46,6 +60,10 @@ final class DiffusionServeController extends DiffusionController { } else if ($content_type == 'application/x-git-receive-pack-request') { // We get this for `git-receive-pack`. $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + } else if (preg_match($lfs_pattern, $content_type)) { + // This is a Git LFS HTTP API request. + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $this->isGitLFSRequest = true; } else if ($request->getExists('cmd')) { // Mercurial also sends an Accept header like // "application/mercurial-0.1", and a User-Agent like @@ -142,7 +160,17 @@ final class DiffusionServeController extends DiffusionController { $username = $_SERVER['PHP_AUTH_USER']; $password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']); - $viewer = $this->authenticateHTTPRepositoryUser($username, $password); + // Try Git LFS auth first since we can usually reject it without doing + // any queries, since the username won't match the one we expect or the + // request won't be LFS. + $viewer = $this->authenticateGitLFSUser($username, $password); + + // If that failed, try normal auth. Note that we can use normal auth on + // LFS requests, so this isn't strictly an alternative to LFS auth. + if (!$viewer) { + $viewer = $this->authenticateHTTPRepositoryUser($username, $password); + } + if (!$viewer) { return new PhabricatorVCSResponse( 403, @@ -202,6 +230,11 @@ final class DiffusionServeController extends DiffusionController { } } + $response = $this->validateGitLFSRequest($repository, $viewer); + if ($response) { + return $response; + } + $this->setServiceRepository($repository); if (!$repository->isTracked()) { @@ -212,46 +245,57 @@ final class DiffusionServeController extends DiffusionController { $is_push = !$this->isReadOnlyRequest($repository); - switch ($repository->getServeOverHTTP()) { - case PhabricatorRepository::SERVE_READONLY: - if ($is_push) { + if ($this->getIsGitLFSRequest() && $this->getGitLFSToken()) { + // We allow git LFS requests over HTTP even if the repository does not + // otherwise support HTTP reads or writes, as long as the user is using a + // token from SSH. If they're using HTTP username + password auth, they + // have to obey the normal HTTP rules. + } else { + switch ($repository->getServeOverHTTP()) { + case PhabricatorRepository::SERVE_READONLY: + if ($is_push) { + return new PhabricatorVCSResponse( + 403, + pht('This repository is read-only over HTTP.')); + } + break; + case PhabricatorRepository::SERVE_READWRITE: + // We'll check for push capability below. + break; + case PhabricatorRepository::SERVE_OFF: + default: return new PhabricatorVCSResponse( 403, - pht('This repository is read-only over HTTP.')); - } - break; - case PhabricatorRepository::SERVE_READWRITE: - if ($is_push) { - $can_push = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - DiffusionPushCapability::CAPABILITY); - if (!$can_push) { - if ($viewer->isLoggedIn()) { - return new PhabricatorVCSResponse( - 403, - pht('You do not have permission to push to this repository.')); - } else { - if ($allow_auth) { - return new PhabricatorVCSResponse( - 401, - pht('You must log in to push to this repository.')); - } else { - return new PhabricatorVCSResponse( - 403, - pht( - 'Pushing to this repository requires authentication, '. - 'which is forbidden over HTTP.')); - } - } + pht('This repository is not available over HTTP.')); + } + } + + if ($is_push) { + $can_push = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + DiffusionPushCapability::CAPABILITY); + if (!$can_push) { + if ($viewer->isLoggedIn()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'You do not have permission to push to this '. + 'repository.')); + } else { + if ($allow_auth) { + return new PhabricatorVCSResponse( + 401, + pht('You must log in to push to this repository.')); + } else { + return new PhabricatorVCSResponse( + 403, + pht( + 'Pushing to this repository requires authentication, '. + 'which is forbidden over HTTP.')); } } - break; - case PhabricatorRepository::SERVE_OFF: - default: - return new PhabricatorVCSResponse( - 403, - pht('This repository is not available over HTTP.')); + } } $vcs_type = $repository->getVersionControlSystem(); @@ -324,6 +368,14 @@ final class DiffusionServeController extends DiffusionController { PhabricatorRepository $repository, PhabricatorUser $viewer) { + // We can serve Git LFS requests first, since we don't need to proxy them. + // It's also important that LFS requests never fall through to standard + // service pathways, because that would let you use LFS tokens to read + // normal repository data. + if ($this->getIsGitLFSRequest()) { + return $this->serveGitLFSRequest($repository, $viewer); + } + // If this repository is hosted on a service, we need to proxy the request // to a host which can serve it. $is_cluster_request = $this->getRequest()->isProxiedClusterRequest(); @@ -363,6 +415,8 @@ final class DiffusionServeController extends DiffusionController { // TODO: This implementation is safe by default, but very incomplete. + // TODO: This doesn't get the right result for Git LFS yet. + switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $service = $request->getStr('service'); @@ -514,6 +568,52 @@ final class DiffusionServeController extends DiffusionController { return $base_path; } + private function authenticateGitLFSUser( + $username, + PhutilOpaqueEnvelope $password) { + + // Never accept these credentials for requests which aren't LFS requests. + if (!$this->getIsGitLFSRequest()) { + return null; + } + + // If we have the wrong username, don't bother checking if the token + // is right. + if ($username !== DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME) { + return null; + } + + $lfs_pass = $password->openEnvelope(); + $lfs_hash = PhabricatorHash::digest($lfs_pass); + + $token = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE)) + ->withTokenCodes(array($lfs_hash)) + ->withExpired(false) + ->executeOne(); + if (!$token) { + return null; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($token->getUserPHID())) + ->executeOne(); + + if (!$user) { + return null; + } + + if (!$user->isUserActivated()) { + return null; + } + + $this->gitLFSToken = $token; + + return $user; + } + private function authenticateHTTPRepositoryUser( $username, PhutilOpaqueEnvelope $password) { @@ -739,4 +839,68 @@ final class DiffusionServeController extends DiffusionController { ); } + private function validateGitLFSRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + if (!$this->getIsGitLFSRequest()) { + return null; + } + + if (!$repository->canUseGitLFS()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'The requested repository ("%s") does not support Git LFS.', + $repository->getDisplayName())); + } + + // If this is using an LFS token, sanity check that we're using it on the + // correct repository. This shouldn't really matter since the user could + // just request a proper token anyway, but it suspicious and should not + // be permitted. + + $token = $this->getGitLFSToken(); + if ($token) { + $resource = $token->getTokenResource(); + if ($resource !== $repository->getPHID()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'The authentication token provided in the request is bound to '. + 'a different repository than the requested repository ("%s").', + $repository->getDisplayName())); + } + } + + return null; + } + + private function serveGitLFSRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + + if (!$this->getIsGitLFSRequest()) { + throw new Exception(pht('This is not a Git LFS request!')); + } + + $path = $this->getGitLFSRequestPath($repository); + + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS operation "%s" is not supported by this server.', + $path)); + } + + private function getGitLFSRequestPath(PhabricatorRepository $repository) { + $request_path = $this->getRequestDirectoryPath($repository); + + $matches = null; + if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) { + return $matches[1]; + } + + return null; + } + } diff --git a/src/applications/diffusion/response/DiffusionGitLFSResponse.php b/src/applications/diffusion/response/DiffusionGitLFSResponse.php new file mode 100644 index 0000000000..513dc577e9 --- /dev/null +++ b/src/applications/diffusion/response/DiffusionGitLFSResponse.php @@ -0,0 +1,37 @@ +setHTTPResponseCode($code) + ->setContent( + array( + 'message' => $message, + )); + } + + public function setContent(array $content) { + $this->content = phutil_json_encode($content); + return $this; + } + + public function buildResponseString() { + return $this->content; + } + + public function getHeaders() { + $headers = array( + array('Content-Type', 'application/vnd.git-lfs+json'), + ); + + return array_merge(parent::getHeaders(), $headers); + } + +} From 8f94aa8a0647af1dfaad9fa2c1c919841d466749 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 17 Mar 2016 12:01:22 -0700 Subject: [PATCH 16/25] Update Diffusion UI Summary: This updates (all?) of Diffusion/Audit to new UI, included edit and other extra form pages. It's fairly complete but I don't know all the nooks and crannies so to speak to fully verify I didn't mess anything up. Test Plan: Tested creating new repositories, browsing, searching, auditing. Need more eyes. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15487 --- resources/celerity/map.php | 36 +- .../PhabricatorAuditCommitStatusConstants.php | 8 +- .../constants/DifferentialChangeType.php | 15 + .../view/DifferentialChangesetListView.php | 16 +- .../DiffusionBranchTableController.php | 26 +- .../controller/DiffusionBrowseController.php | 318 +++++++++------- .../controller/DiffusionChangeController.php | 68 ++-- .../controller/DiffusionCommitController.php | 358 ++++++++++-------- .../controller/DiffusionController.php | 22 ++ .../controller/DiffusionHistoryController.php | 110 +++--- .../controller/DiffusionLintController.php | 31 +- .../DiffusionRepositoryController.php | 241 +++++++----- .../DiffusionRepositoryCreateController.php | 18 +- ...ffusionRepositoryEditActionsController.php | 16 +- ...sionRepositoryEditAutomationController.php | 17 +- ...DiffusionRepositoryEditBasicController.php | 17 +- ...fusionRepositoryEditBranchesController.php | 14 +- .../DiffusionRepositoryEditController.php | 1 + ...fusionRepositoryEditEncodingController.php | 16 +- ...ffusionRepositoryEditHostingController.php | 33 +- .../DiffusionRepositoryEditMainController.php | 112 +++--- ...ffusionRepositoryEditStagingController.php | 18 +- ...ffusionRepositoryEditStorageController.php | 19 +- ...sionRepositoryEditSubversionController.php | 14 +- .../DiffusionRepositorySymbolsController.php | 18 +- .../controller/DiffusionSymbolController.php | 15 +- .../controller/DiffusionTagListController.php | 30 +- .../view/DiffusionBrowseTableView.php | 5 - .../diffusion/view/DiffusionView.php | 4 +- src/view/form/PHUIInfoView.php | 9 + src/view/form/PHUIPagedFormView.php | 1 + src/view/phui/PHUIHeadThingView.php | 10 +- .../application/diffusion/diffusion-icons.css | 8 + .../diffusion/diffusion-source.css | 14 +- webroot/rsrc/css/phui/phui-box.css | 19 +- webroot/rsrc/css/phui/phui-head-thing.css | 3 + webroot/rsrc/css/phui/phui-header-view.css | 4 + webroot/rsrc/css/phui/phui-info-view.css | 4 + .../rsrc/css/phui/phui-property-list-view.css | 1 - .../rsrc/css/phui/phui-two-column-view.css | 19 +- 40 files changed, 1049 insertions(+), 659 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c4e3ae3bf6..3209d1c6ed 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,12 +7,12 @@ */ return array( 'names' => array( - 'core.pkg.css' => '225e8ac7', + 'core.pkg.css' => 'a93de192', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', 'differential.pkg.js' => 'd0cd0df6', - 'diffusion.pkg.css' => 'f45955ed', + 'diffusion.pkg.css' => 'dc8e0cc2', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', @@ -64,9 +64,9 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => '2941baf1', + 'rsrc/css/application/diffusion/diffusion-icons.css' => '3311444d', 'rsrc/css/application/diffusion/diffusion-readme.css' => '356a4f3c', - 'rsrc/css/application/diffusion/diffusion-source.css' => '075ba788', + 'rsrc/css/application/diffusion/diffusion-source.css' => '68b30fd3', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => '5337623f', @@ -123,7 +123,7 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '96a10c5d', + 'rsrc/css/phui/phui-box.css' => 'b2d49bae', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', @@ -135,28 +135,28 @@ return array( 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', - 'rsrc/css/phui/phui-head-thing.css' => '31638812', - 'rsrc/css/phui/phui-header-view.css' => '26cffd3d', + 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', + 'rsrc/css/phui/phui-header-view.css' => '230254d3', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', + 'rsrc/css/phui/phui-info-view.css' => '28efab79', 'rsrc/css/phui/phui-list.css' => '9da2aa00', 'rsrc/css/phui/phui-object-box.css' => '6b487c57', 'rsrc/css/phui/phui-object-item-list-view.css' => '18b2ce8e', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-profile-menu.css' => '7e92a89a', - 'rsrc/css/phui/phui-property-list-view.css' => 'b12e801c', + 'rsrc/css/phui/phui-property-list-view.css' => '1d42ee7c', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'a0173eba', - 'rsrc/css/phui/phui-two-column-view.css' => '61dd6d38', + 'rsrc/css/phui/phui-two-column-view.css' => 'c110d0c3', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -553,9 +553,9 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => '2941baf1', + 'diffusion-icons-css' => '3311444d', 'diffusion-readme-css' => '356a4f3c', - 'diffusion-source-css' => '075ba788', + 'diffusion-source-css' => '68b30fd3', 'diviner-shared-css' => 'aa3656aa', 'font-aleo' => '8bdb2835', 'font-fontawesome' => 'c43323c5', @@ -805,7 +805,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '96a10c5d', + 'phui-box-css' => 'b2d49bae', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', @@ -822,15 +822,15 @@ return array( 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', - 'phui-head-thing-view-css' => '31638812', - 'phui-header-view-css' => '26cffd3d', + 'phui-head-thing-view-css' => 'fd311e5f', + 'phui-header-view-css' => '230254d3', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => '3f33ab57', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => '6d7c3509', + 'phui-info-view-css' => '28efab79', 'phui-inline-comment-view-css' => '5953c28e', 'phui-list-view-css' => '9da2aa00', 'phui-object-box-css' => '6b487c57', @@ -838,7 +838,7 @@ return array( 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-profile-menu-css' => '7e92a89a', - 'phui-property-list-view-css' => 'b12e801c', + 'phui-property-list-view-css' => '1d42ee7c', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', @@ -846,7 +846,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'a0173eba', - 'phui-two-column-view-css' => '61dd6d38', + 'phui-two-column-view-css' => 'c110d0c3', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php index c96f22e338..ca959d4aef 100644 --- a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php +++ b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php @@ -37,9 +37,11 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject { $color = 'red'; break; case self::NEEDS_AUDIT: - case self::PARTIALLY_AUDITED: $color = 'orange'; break; + case self::PARTIALLY_AUDITED: + $color = 'yellow'; + break; case self::FULLY_AUDITED: $color = 'green'; break; @@ -53,11 +55,11 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject { public static function getStatusIcon($code) { switch ($code) { case self::CONCERN_RAISED: - $icon = 'fa-exclamation-triangle'; + $icon = 'fa-exclamation-circle'; break; case self::NEEDS_AUDIT: case self::PARTIALLY_AUDITED: - $icon = 'fa-exclamation-triangle'; + $icon = 'fa-exclamation-circle'; break; case self::FULLY_AUDITED: $icon = 'fa-check'; diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php index 2bf4b217a7..7ee3c89b65 100644 --- a/src/applications/differential/constants/DifferentialChangeType.php +++ b/src/applications/differential/constants/DifferentialChangeType.php @@ -81,6 +81,21 @@ final class DifferentialChangeType extends Phobject { return idx($icons, $type, 'fa-file'); } + public static function getIconColorForFileType($type) { + static $icons = array( + self::FILE_TEXT => 'black', + self::FILE_IMAGE => 'black', + self::FILE_BINARY => 'green', + self::FILE_DIRECTORY => 'blue', + self::FILE_SYMLINK => 'blue', + self::FILE_DELETED => 'red', + self::FILE_NORMAL => 'black', + self::FILE_SUBMODULE => 'blue', + ); + + return idx($icons, $type, 'black'); + } + public static function isOldLocationChangeType($type) { static $types = array( self::TYPE_MOVE_AWAY => true, diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 4f173bda53..f0c611c609 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -9,6 +9,7 @@ final class DifferentialChangesetListView extends AphrontView { private $renderURI = '/differential/changeset/'; private $whitespace; private $background; + private $header; private $standaloneURI; private $leftRawFileURI; @@ -118,6 +119,11 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setHeader($header) { + $this->header = $header; + return $this; + } + public function render() { $viewer = $this->getViewer(); @@ -246,8 +252,12 @@ final class DifferentialChangesetListView extends AphrontView { )); } - $header = id(new PHUIHeaderView()) - ->setHeader($this->getTitle()); + if ($this->header) { + $header = $this->header; + } else { + $header = id(new PHUIHeaderView()) + ->setHeader($this->getTitle()); + } $content = phutil_tag( 'div', @@ -259,8 +269,8 @@ final class DifferentialChangesetListView extends AphrontView { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) - ->setCollapsed(true) ->setBackground($this->background) + ->setCollapsed(true) ->appendChild($content); return $object_box; diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index 3a025e90c5..5c76469b5e 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -48,26 +48,37 @@ final class DiffusionBranchTableController extends DiffusionController { ->withRepository($repository) ->execute(); - $view = id(new DiffusionBranchTableView()) + $table = id(new DiffusionBranchTableView()) ->setUser($viewer) ->setBranches($branches) ->setCommits($commits) ->setDiffusionRequest($drequest); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Branches')) - ->setTable($view); - - $content = $panel; + $content = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); } $crumbs = $this->buildCrumbs( array( 'branches' => true, )); + $crumbs->setBorder(true); $pager_box = $this->renderTablePagerBox($pager); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Branches')) + ->setHeaderIcon('fa-code-fork'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $content, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -77,8 +88,7 @@ final class DiffusionBranchTableController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index a2e9a3a4b3..81adacf7c9 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -55,20 +55,17 @@ final class DiffusionBrowseController extends DiffusionController { } private function browseSearch() { + $drequest = $this->getDiffusionRequest(); + $header = $this->buildHeaderView($drequest); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); + $search_form = $this->renderSearchForm(); + $search_results = $this->renderSearchResults(); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = false); - $content[] = $this->renderSearchResults(); + $search_form = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Search')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($search_form); $crumbs = $this->buildCrumbs( array( @@ -76,6 +73,14 @@ final class DiffusionBrowseController extends DiffusionController { 'path' => true, 'view' => 'browse', )); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $search_form, + $search_results, + )); return $this->newPage() ->setTitle( @@ -84,7 +89,7 @@ final class DiffusionBrowseController extends DiffusionController { $drequest->getRepository()->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild($view); } private function browseFile() { @@ -218,20 +223,18 @@ final class DiffusionBrowseController extends DiffusionController { require_celerity_resource('diffusion-source-css'); // Render the page. - $view = $this->buildActionView($drequest); - $action_list = $this->enrichActionView( + $view = $this->buildCurtain($drequest); + $curtain = $this->enrichCurtain( $view, $drequest, $show_blame, $show_color); - $properties = $this->buildPropertyView($drequest, $action_list); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); + $properties = $this->buildPropertyView($drequest); + $header = $this->buildHeaderView($drequest); + $header->setHeaderIcon('fa-file-code-o'); $content = array(); - $content[] = $object_box; $follow = $request->getStr('follow'); if ($follow) { @@ -277,17 +280,31 @@ final class DiffusionBrowseController extends DiffusionController { 'path' => true, 'view' => 'browse', )); + $crumbs->setBorder(true); $basename = basename($this->getDiffusionRequest()->getPath()); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $content, + )); + + if ($properties) { + $view->addPropertySection(pht('DETAILS'), $properties); + } + + $title = array($basename, $repository->getDisplayName()); + return $this->newPage() - ->setTitle( - array( - $basename, - $repository->getDisplayName(), - )) + ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild( + array( + $view, + )); + } public function browseDirectory( @@ -300,23 +317,21 @@ final class DiffusionBrowseController extends DiffusionController { $reason = $results->getReasonForEmptyResultSet(); - $content = array(); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); + $curtain = $this->buildCurtain($drequest); + $details = $this->buildPropertyView($drequest); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); + $header = $this->buildHeaderView($drequest); + $header->setHeaderIcon('fa-folder-open'); - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = true); + $search_form = $this->renderSearchForm(); + $empty_result = null; + $browse_panel = null; if (!$results->isValidResults()) { $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); $empty_result->setDiffusionBrowseResultSet($results); $empty_result->setView($request->getStr('view')); - $content[] = $empty_result; } else { $phids = array(); foreach ($results->getPaths() as $result) { @@ -331,21 +346,30 @@ final class DiffusionBrowseController extends DiffusionController { $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); - $browse_table = new DiffusionBrowseTableView(); - $browse_table->setDiffusionRequest($drequest); - $browse_table->setHandles($handles); - $browse_table->setPaths($results->getPaths()); - $browse_table->setUser($request->getUser()); + $browse_table = id(new DiffusionBrowseTableView()) + ->setDiffusionRequest($drequest) + ->setHandles($handles) + ->setPaths($results->getPaths()) + ->setUser($request->getUser()); - $browse_panel = new PHUIObjectBoxView(); - $browse_panel->setHeaderText($drequest->getPath(), '/'); - $browse_panel->setTable($browse_table); + $browse_header = id(new PHUIHeaderView()) + ->setHeader(nonempty(basename($drequest->getPath()), '/')) + ->setHeaderIcon('fa-folder-open'); - $content[] = $browse_panel; + $browse_panel = id(new PHUIObjectBoxView()) + ->setHeader($browse_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($browse_table); + + $browse_panel->setShowHide( + array(pht('Show Search')), + pht('Hide Search'), + $search_form, + '#'); } - $content[] = $this->buildOpenRevisions(); - $content[] = $this->renderDirectoryReadme($results); + $open_revisions = $this->buildOpenRevisions(); + $readme = $this->renderDirectoryReadme($results); $crumbs = $this->buildCrumbs( array( @@ -355,18 +379,34 @@ final class DiffusionBrowseController extends DiffusionController { )); $pager_box = $this->renderTablePagerBox($pager); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $empty_result, + $browse_panel, + )) + ->setFooter(array( + $open_revisions, + $readme, + $pager_box, + )); + + if ($details) { + $view->addPropertySection(pht('DETAILS'), $details); + } return $this->newPage() - ->setTitle( - array( + ->setTitle(array( nonempty(basename($drequest->getPath()), '/'), $repository->getDisplayName(), )) ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } @@ -431,6 +471,7 @@ final class DiffusionBrowseController extends DiffusionController { $box = id(new PHUIObjectBoxView()) ->setHeaderText($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $pager_box = $this->renderTablePagerBox($pager); @@ -697,12 +738,14 @@ final class DiffusionBrowseController extends DiffusionController { $edit = $this->renderEditButton(); $file = $this->renderFileButton(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('File Contents')) + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->setHeaderIcon('fa-file-code-o') ->addActionLink($edit) ->addActionLink($file); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($corpus) ->setCollapsed(true); @@ -737,16 +780,16 @@ final class DiffusionBrowseController extends DiffusionController { return $corpus; } - private function enrichActionView( - PhabricatorActionListView $view, + private function enrichCurtain( + PHUICurtainView $curtain, DiffusionRequest $drequest, $show_blame, $show_color) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $base_uri = $this->getRequest()->getRequestURI(); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Last Change')) ->setHref( @@ -766,7 +809,7 @@ final class DiffusionBrowseController extends DiffusionController { $blame_value = 1; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($blame_text) ->setHref($base_uri->alter('blame', $blame_value)) @@ -784,7 +827,7 @@ final class DiffusionBrowseController extends DiffusionController { $highlight_value = 1; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($highlight_text) ->setHref($base_uri->alter('color', $highlight_value)) @@ -809,14 +852,57 @@ final class DiffusionBrowseController extends DiffusionController { ))->alter('lint', ''); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($lint_text) ->setHref($href) ->setIcon('fa-exclamation-triangle') ->setDisabled(!$href)); - return $view; + + $repository = $drequest->getRepository(); + + $owners = 'PhabricatorOwnersApplication'; + if (PhabricatorApplication::isClassInstalled($owners)) { + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl( + $repository->getPHID(), + array( + $drequest->getPath(), + )); + + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); + + if ($packages) { + $ownership = id(new PHUIStatusListView()) + ->setUser($viewer); + + foreach ($packages as $package) { + $icon = 'fa-list-alt'; + $color = 'grey'; + + $item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($viewer->renderHandle($package->getPHID())); + + $ownership->addItem($item); + } + } else { + $ownership = phutil_tag('em', array(), pht('None')); + } + + $curtain->newPanel() + ->setHeaderText(pht('Owners')) + ->appendChild($ownership); + } + + return $curtain; } private function renderEditButton() { @@ -1265,11 +1351,13 @@ final class DiffusionBrowseController extends DiffusionController { $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Image')) - ->addActionLink($file); + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->addActionLink($file) + ->setHeaderIcon('fa-file-image-o'); return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } @@ -1282,11 +1370,12 @@ final class DiffusionBrowseController extends DiffusionController { $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) + ->setHeader(pht('DETAILS')) ->addActionLink($file); $box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($text); return $box; @@ -1298,7 +1387,7 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($message); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')); + ->setHeader(pht('DETAILS')); $box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -1461,12 +1550,12 @@ final class DiffusionBrowseController extends DiffusionController { return "{$summary}\n{$date} \xC2\xB7 {$author}"; } - protected function renderSearchForm($collapsed) { + protected function renderSearchForm() { $drequest = $this->getDiffusionRequest(); $forms = array(); $form = id(new AphrontFormView()) - ->setUser($this->getRequest()->getUser()) + ->setUser($this->getViewer()) ->setMethod('GET'); switch ($drequest->getRepository()->getVersionControlSystem()) { @@ -1492,22 +1581,10 @@ final class DiffusionBrowseController extends DiffusionController { break; } - $filter = new AphrontListFilterView(); - $filter->appendChild($forms); + require_celerity_resource('diffusion-icons-css'); + $form_box = phutil_tag_div('diffusion-search-boxen', $forms); - if ($collapsed) { - $filter->setCollapsed( - pht('Show Search'), - pht('Hide Search'), - pht('Search for file names or content in this directory.'), - '#'); - } - - $filter = id(new PHUIBoxView()) - ->addClass('mlt mlb') - ->appendChild($filter); - - return $filter; + return $form_box; } protected function markupText($text) { @@ -1526,28 +1603,29 @@ final class DiffusionBrowseController extends DiffusionController { } protected function buildHeaderView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + + $tag = $this->renderCommitHashTag($drequest); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($this->renderPathLinks($drequest, $mode = 'browse')) - ->setPolicyObject($drequest->getRepository()); + ->addTag($tag); return $header; } - protected function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + protected function buildCurtain(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($drequest); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setHref($history_uri) @@ -1559,40 +1637,22 @@ final class DiffusionBrowseController extends DiffusionController { 'commit' => '', 'action' => 'browse', )); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Jump to HEAD')) ->setHref($head_uri) ->setIcon('fa-home') ->setDisabled(!$behind_head)); - return $view; + return $curtain; } protected function buildPropertyView( - DiffusionRequest $drequest, - PhabricatorActionListView $actions) { + DiffusionRequest $drequest) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $stable_commit = $drequest->getStableCommit(); - - $view->addProperty( - pht('Commit'), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $stable_commit, - )), - ), - $drequest->getRepository()->formatCommitName($stable_commit))); + ->setUser($viewer); if ($drequest->getSymbolicType() == 'tag') { $symbolic = $drequest->getSymbolicCommit(); @@ -1616,47 +1676,11 @@ final class DiffusionBrowseController extends DiffusionController { } } - $repository = $drequest->getRepository(); - - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $package_query = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withControl( - $repository->getPHID(), - array( - $drequest->getPath(), - )); - - $package_query->execute(); - - $packages = $package_query->getControllingPackagesForPath( - $repository->getPHID(), - $drequest->getPath()); - - if ($packages) { - $ownership = id(new PHUIStatusListView()) - ->setUser($viewer); - - foreach ($packages as $package) { - $icon = 'fa-list-alt'; - $color = 'grey'; - - $item = id(new PHUIStatusItemView()) - ->setIcon($icon, $color) - ->setTarget($viewer->renderHandle($package->getPHID())); - - $ownership->addItem($item); - } - } else { - $ownership = phutil_tag('em', array(), pht('None')); - } - - $view->addProperty(pht('Packages'), $ownership); + if ($view->hasAnyProperties()) { + return $view; } - return $view; + return null; } private function buildOpenRevisions() { diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index 86b371c38b..90258134c4 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -15,8 +15,6 @@ final class DiffusionChangeController extends DiffusionController { $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); - $content = array(); - $data = $this->callConduitWithDiffusionRequest( 'diffusion.diffquery', array( @@ -42,9 +40,11 @@ final class DiffusionChangeController extends DiffusionController { 0 => $changeset, ); + $changeset_header = $this->buildChangesetHeader($drequest); + $changeset_view = new DifferentialChangesetListView(); - $changeset_view->setTitle(pht('Change')); $changeset_view->setChangesets($changesets); + $changeset_view->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $changeset_view->setVisibleChangesets($changesets); $changeset_view->setRenderingReferences( array( @@ -68,11 +68,11 @@ final class DiffusionChangeController extends DiffusionController { $changeset_view->setWhitespace( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $changeset_view->setUser($viewer); + $changeset_view->setHeader($changeset_header); // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); - $content[] = $changeset_view->render(); $crumbs = $this->buildCrumbs( array( @@ -80,19 +80,18 @@ final class DiffusionChangeController extends DiffusionController { 'path' => true, 'view' => 'change', )); + $crumbs->setBorder(true); $links = $this->renderPathLinks($drequest, $mode = 'browse'); + $header = $this->buildHeader($drequest, $links); - $header = id(new PHUIHeaderView()) - ->setHeader($links) - ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setMainColumn(array( + )) + ->setFooter(array( + $changeset_view, + )); return $this->newPage() ->setTitle( @@ -103,25 +102,41 @@ final class DiffusionChangeController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, + $view, )); } - private function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader( + DiffusionRequest $drequest, + $links) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $tag = $this->renderCommitHashTag($drequest); + + $header = id(new PHUIHeaderView()) + ->setHeader($links) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag); + + return $header; + } + + private function buildChangesetHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Changes')); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View History')) ->setHref($history_uri) ->setIcon('fa-clock-o')); @@ -130,13 +145,14 @@ final class DiffusionChangeController extends DiffusionController { 'action' => 'browse', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Browse Content')) + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Browse Content')) ->setHref($browse_uri) ->setIcon('fa-files-o')); - return $view; + return $header; } protected function buildPropertyView( diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 94ca15ac4f..b14477ffa3 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -24,8 +24,7 @@ final class DiffusionCommitController extends DiffusionController { } $drequest = $this->getDiffusionRequest(); - - $user = $request->getUser(); + $viewer = $request->getUser(); if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); @@ -33,9 +32,8 @@ final class DiffusionCommitController extends DiffusionController { $repository = $drequest->getRepository(); - $content = array(); $commit = id(new DiffusionCommitQuery()) - ->setViewer($request->getUser()) + ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers(array($drequest->getCommit())) ->needCommitData(true) @@ -45,6 +43,7 @@ final class DiffusionCommitController extends DiffusionController { $crumbs = $this->buildCrumbs(array( 'commit' => true, )); + $crumbs->setBorder(true); if (!$commit) { if (!$this->getCommitExists()) { @@ -70,10 +69,11 @@ final class DiffusionCommitController extends DiffusionController { $audit_requests = $commit->getAudits(); $this->auditAuthorityPHIDs = - PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); + PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer); $commit_data = $commit->getCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); + $error_panel = null; if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); @@ -87,43 +87,41 @@ final class DiffusionCommitController extends DiffusionController { "didn't affect the tracked subdirectory ('%s'), so no ". "information is available.", $subpath)); - $content[] = $error_panel; } else { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $engine->setConfig('viewer', $user); + $engine->setConfig('viewer', $viewer); - $headsup_view = id(new PHUIHeaderView()) + $commit_tag = $this->renderCommitHashTag($drequest); + $header = id(new PHUIHeaderView()) ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail'))) - ->setSubheader(pht('Commit: %s', $commit->getCommitIdentifier())); + ->setHeaderIcon('fa-code-fork') + ->addTag($commit_tag); - $headsup_actions = $this->renderHeadsupActionList($commit, $repository); + if ($commit->getAuditStatus()) { + $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon( + $commit->getAuditStatus()); + $color = PhabricatorAuditCommitStatusConstants::getStatusColor( + $commit->getAuditStatus()); + $status = PhabricatorAuditCommitStatusConstants::getStatusName( + $commit->getAuditStatus()); - $commit_properties = $this->loadCommitProperties( + $header->setStatus($icon, $color, $status); + } + + $curtain = $this->buildCurtain($commit, $repository); + $subheader = $this->buildSubheaderView($commit, $commit_data); + $details = $this->buildPropertyListView( $commit, $commit_data, $audit_requests); - $property_list = id(new PHUIPropertyListView()) - ->setHasKeyboardShortcuts(true) - ->setUser($user) - ->setObject($commit); - foreach ($commit_properties as $key => $value) { - $property_list->addProperty($key, $value); - } $message = $commit_data->getCommitMessage(); $revision = $commit->getCommitIdentifier(); $message = $this->linkBugtraq($message); - $message = $engine->markupText($message); - $property_list->invokeWillRenderEvent(); - $property_list->setActionList($headsup_actions); - $detail_list = new PHUIPropertyListView(); - $detail_list->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); $detail_list->addTextContent( phutil_tag( 'div', @@ -132,19 +130,14 @@ final class DiffusionCommitController extends DiffusionController { ), $message)); - $headsup_view->setTall(true); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($headsup_view) - ->setFormErrors($this->getCommitErrors()) - ->addPropertyList($property_list) - ->addPropertyList($detail_list); - - $content[] = $object_box; + if ($this->getCommitErrors()) { + $error_panel = id(new PHUIInfoView()) + ->appendChild($this->getCommitErrors()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + } } - $content[] = $this->buildComments($commit); - + $timeline = $this->buildComments($commit); $hard_limit = 1000; if ($commit->isImported()) { @@ -161,10 +154,10 @@ final class DiffusionCommitController extends DiffusionController { $changes = array_slice($changes, 0, $hard_limit); } - $content[] = $this->buildMergesTable($commit); + $merge_table = $this->buildMergesTable($commit); $highlighted_audits = $commit->getAuthorityAudits( - $user, + $viewer, $this->auditAuthorityPHIDs); $count = count($changes); @@ -179,32 +172,35 @@ final class DiffusionCommitController extends DiffusionController { } $show_changesets = false; + $info_panel = null; + $change_list = null; + $change_table = null; if ($bad_commit) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Bad Commit'), $bad_commit['description']); } else if ($is_foreign) { // Don't render anything else. } else if (!$commit->isImported()) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Still Importing...'), pht( 'This commit is still importing. Changes will be visible once '. 'the import finishes.')); } else if (!count($changes)) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Empty Commit'), pht( 'This commit is empty and does not affect any paths.')); } else if ($was_limited) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Enormous Commit'), pht( 'This commit is enormous, and affects more than %d files. '. 'Changes are not shown.', $hard_limit)); } else if (!$this->getCommitExists()) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Commit No Longer Exists'), pht('This commit no longer exists in the repository.')); } else { @@ -214,7 +210,7 @@ final class DiffusionCommitController extends DiffusionController { // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all'); - $header = id(new PHUIHeaderView()) + $change_header = id(new PHUIHeaderView()) ->setHeader(pht('Changes (%s)', new PhutilNumber($count))); $warning_view = null; @@ -228,24 +224,23 @@ final class DiffusionCommitController extends DiffusionController { $warning_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle(pht('Very Large Commit')) - ->addButton($button) ->appendChild( pht('This commit is very large. Load each file individually.')); + + $change_header->addActionLink($button); } $changesets = DiffusionPathChange::convertToDifferentialChangesets( - $user, + $viewer, $changes); // TODO: This table and panel shouldn't really be separate, but we need // to clean up the "Load All Files" interaction first. $change_table = $this->buildTableOfContents( $changesets, - $header, + $change_header, $warning_view); - $content[] = $change_table; - $vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: @@ -291,7 +286,7 @@ final class DiffusionCommitController extends DiffusionController { } else { $visible_changesets = array(); $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments( - $user, + $viewer, $commit->getPHID()); $path_ids = mpull($inlines, null, 'getPathID'); foreach ($changesets as $key => $changeset) { @@ -308,10 +303,10 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); - $change_list->setRenderURI( - $repository->getPathURI('diff/')); + $change_list->setRenderURI($repository->getPathURI('diff/')); $change_list->setRepository($repository); - $change_list->setUser($user); + $change_list->setUser($viewer); + $change_list->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); // TODO: Try to setBranch() to something reasonable here? @@ -327,48 +322,74 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); - $content[] = $change_list->render(); } - $content[] = $this->renderAddCommentPanel($commit, $audit_requests); + $add_comment = $this->renderAddCommentPanel($commit, $audit_requests); - $prefs = $user->loadPreferences(); + $prefs = $viewer->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED; $show_filetree = $prefs->getPreference($pref_filetree); $collapsed = $prefs->getPreference($pref_collapse); + $nav = null; if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($commit->getDisplayName()) ->setBaseURI(new PhutilURI($commit->getURI())) ->build($changesets) ->setCrumbs($crumbs) - ->setCollapsed((bool)$collapsed) - ->appendChild($content); - $content = $nav; - } else { - $content = array($crumbs, $content); + ->setCollapsed((bool)$collapsed); } - return $this->buildApplicationPage( - $content, - array( - 'title' => $commit->getDisplayName(), - 'pageObjects' => array($commit->getPHID()), + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setMainColumn(array( + $error_panel, + $timeline, + $merge_table, + $info_panel, + )) + ->setFooter(array( + $change_table, + $change_list, + $add_comment, + )) + ->addPropertySection(pht('DESCRIPTION'), $detail_list) + ->addPropertySection(pht('DETAILS'), $details) + ->setCurtain($curtain); + + $page = $this->newPage() + ->setTitle($commit->getDisplayName()) + ->setCrumbs($crumbs) + ->setPageObjectPHIDS(array($commit->getPHID())) + ->appendChild( + array( + $view, )); + + if ($nav) { + $page->setNavigation($nav); + } + + return $page; + } - private function loadCommitProperties( + private function buildPropertyListView( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $audit_requests) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $view = id(new PHUIPropertyListView()) + ->setUser($this->getRequest()->getUser()); + $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) ->withEdgeTypes(array( @@ -432,31 +453,6 @@ final class DiffusionCommitController extends DiffusionController { $props = array(); - if ($commit->getAuditStatus()) { - $status = PhabricatorAuditCommitStatusConstants::getStatusName( - $commit->getAuditStatus()); - $tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setName($status); - - switch ($commit->getAuditStatus()) { - case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: - $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE); - break; - case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: - $tag->setBackgroundColor(PHUITagView::COLOR_RED); - break; - case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED: - $tag->setBackgroundColor(PHUITagView::COLOR_BLUE); - break; - case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: - $tag->setBackgroundColor(PHUITagView::COLOR_GREEN); - break; - } - - $props['Status'] = $tag; - } - if ($audit_requests) { $user_requests = array(); $other_requests = array(); @@ -469,37 +465,21 @@ final class DiffusionCommitController extends DiffusionController { } if ($user_requests) { - $props['Auditors'] = $this->renderAuditStatusView( - $user_requests); + $view->addProperty( + pht('Auditors'), + $this->renderAuditStatusView($user_requests)); } if ($other_requests) { - $props['Project/Package Auditors'] = $this->renderAuditStatusView( - $other_requests); + $view->addProperty( + pht('Project/Package Auditors'), + $this->renderAuditStatusView($other_requests)); } } $author_phid = $data->getCommitDetail('authorPHID'); $author_name = $data->getAuthorName(); - - if (!$repository->isSVN()) { - $authored_info = id(new PHUIStatusItemView()); - - $author_epoch = $data->getCommitDetail('authorEpoch'); - if ($author_epoch !== null) { - $authored_info->setNote( - phabricator_datetime($author_epoch, $viewer)); - } - - if ($author_phid) { - $authored_info->setTarget($handles[$author_phid]->renderLink()); - } else if (strlen($author_name)) { - $authored_info->setTarget($author_name); - } - - $props['Authored'] = id(new PHUIStatusListView()) - ->addItem($authored_info); - } + $author_epoch = $data->getCommitDetail('authorEpoch'); $committed_info = id(new PHUIStatusItemView()) ->setNote(phabricator_datetime($commit->getEpoch(), $viewer)); @@ -516,8 +496,9 @@ final class DiffusionCommitController extends DiffusionController { $committed_info->setTarget($author_name); } - $props['Committed'] = id(new PHUIStatusListView()) - ->addItem($committed_info); + $view->addProperty( + pht('Committed'), + $committed_info); if ($push_logs) { $pushed_list = new PHUIStatusListView(); @@ -529,36 +510,49 @@ final class DiffusionCommitController extends DiffusionController { $pushed_list->addItem($pushed_item); } - $props['Pushed'] = $pushed_list; + $view->addProperty( + pht('Pushed'), + $pushed_list); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { - $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); + $view->addProperty( + pht('Reviewer'), + $handles[$reviewer_phid]->renderLink()); } if ($revision_phid) { - $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); + $view->addProperty( + pht('Differential Revision'), + $handles[$revision_phid]->renderLink()); } $parents = $this->getCommitParents(); if ($parents) { - $props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID')); + $view->addProperty( + pht('Parents'), + $viewer->renderHandleList(mpull($parents, 'getPHID'))); } if ($this->getCommitExists()) { - $props['Branches'] = phutil_tag( + $view->addProperty( + pht('Branches'), + phutil_tag( 'span', array( 'id' => 'commit-branches', ), - pht('Unknown')); - $props['Tags'] = phutil_tag( + pht('Unknown'))); + + $view->addProperty( + pht('Tags'), + phutil_tag( 'span', array( 'id' => 'commit-tags', ), - pht('Unknown')); + pht('Unknown'))); $identifier = $commit->getCommitIdentifier(); $root = $repository->getPathURI("commit/{$identifier}"); @@ -581,16 +575,21 @@ final class DiffusionCommitController extends DiffusionController { ), $ref_data['ref']); } - $props['References'] = phutil_implode_html(', ', $ref_links); + $view->addProperty( + pht('References'), + phutil_implode_html(', ', $ref_links)); } if ($reverts_phids) { - $props[pht('Reverts')] = $viewer->renderHandleList($reverts_phids); + $view->addProperty( + pht('Reverts'), + $viewer->renderHandleList($reverts_phids)); } if ($reverted_by_phids) { - $props[pht('Reverted By')] = $viewer->renderHandleList( - $reverted_by_phids); + $view->addProperty( + pht('Reverted By'), + $viewer->renderHandleList($reverted_by_phids)); } if ($task_phids) { @@ -599,12 +598,60 @@ final class DiffusionCommitController extends DiffusionController { $task_list[] = $handles[$phid]->renderLink(); } $task_list = phutil_implode_html(phutil_tag('br'), $task_list); - $props['Tasks'] = $task_list; + $view->addProperty( + pht('Tasks'), + $task_list); } - return $props; + return $view; } + private function buildSubheaderView( + PhabricatorRepositoryCommit $commit, + PhabricatorRepositoryCommitData $data) { + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + if ($repository->isSVN()) { + return null; + } + + $author_phid = $data->getCommitDetail('authorPHID'); + $author_name = $data->getAuthorName(); + $author_epoch = $data->getCommitDetail('authorEpoch'); + $date = null; + if ($author_epoch !== null) { + $date = phabricator_datetime($author_epoch, $viewer); + } + + if ($author_phid) { + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + $author = $handles[$author_phid]->renderLink(); + } else if (strlen($author_name)) { + $author = $author_name; + $image_uri = null; + $image_href = null; + } + + $author = phutil_tag('strong', array(), $author); + if ($date) { + $content = pht('Authored by %s on %s.', $author, $date); + } else { + $content = pht('Authored by %s.', $author); + } + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + + } + + private function buildComments(PhabricatorRepositoryCommit $commit) { $timeline = $this->buildTransactionTimeline( $commit, @@ -619,11 +666,11 @@ final class DiffusionCommitController extends DiffusionController { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); - if (!$user->isLoggedIn()) { + if (!$viewer->isLoggedIn()) { return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setRequestURI($request->getRequestURI()); } @@ -638,7 +685,7 @@ final class DiffusionCommitController extends DiffusionController { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft = $draft->getDraft(); @@ -652,7 +699,7 @@ final class DiffusionCommitController extends DiffusionController { $auditor_source = new DiffusionAuditorDatasource(); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setAction('/audit/addcomment/') ->addHiddenInput('commit', $commit->getPHID()) ->appendChild( @@ -685,7 +732,7 @@ final class DiffusionCommitController extends DiffusionController { ->setName('content') ->setValue($draft) ->setID('audit-content') - ->setUser($user)) + ->setUser($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); @@ -771,13 +818,13 @@ final class DiffusionCommitController extends DiffusionController { PhabricatorRepositoryCommit $commit, array $audit_requests) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); - $user_is_author = ($commit->getAuthorPHID() == $user->getPHID()); + $user_is_author = ($commit->getAuthorPHID() == $viewer->getPHID()); $user_request = null; foreach ($audit_requests as $audit_request) { - if ($audit_request->getAuditorPHID() == $user->getPHID()) { + if ($audit_request->getAuditorPHID() == $viewer->getPHID()) { $user_request = $audit_request; break; } @@ -871,9 +918,10 @@ final class DiffusionCommitController extends DiffusionController { $history_table->loadRevisions(); - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Merged Changes')); - $panel->setTable($history_table); + $panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Merged Changes')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($history_table); if ($caption) { $panel->setInfoView($caption); } @@ -881,19 +929,16 @@ final class DiffusionCommitController extends DiffusionController { return $panel; } - private function renderHeadsupActionList( + private function buildCurtain( PhabricatorRepositoryCommit $commit, PhabricatorRepository $repository) { $request = $this->getRequest(); - $user = $request->getUser(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($user) - ->setObject($commit); + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($commit); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, $commit, PhabricatorPolicyCapability::CAN_EDIT); @@ -906,7 +951,7 @@ final class DiffusionCommitController extends DiffusionController { ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); - $actions->addAction($action); + $curtain->addAction($action); require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); @@ -919,16 +964,16 @@ final class DiffusionCommitController extends DiffusionController { ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/') ->setWorkflow(true) ->setDisabled(!$can_edit); - $actions->addAction($action); + $curtain->addAction($action); } $action = id(new PhabricatorActionView()) ->setName(pht('Download Raw Diff')) ->setHref($request->getRequestURI()->alter('diff', true)) ->setIcon('fa-download'); - $actions->addAction($action); + $curtain->addAction($action); - return $actions; + return $curtain; } private function buildRawDiffResponse(DiffusionRequest $drequest) { @@ -1022,7 +1067,8 @@ final class DiffusionCommitController extends DiffusionController { $toc_view = id(new PHUIDiffTableOfContentsListView()) ->setUser($viewer) - ->setHeader($header); + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); if ($info_view) { $toc_view->setInfoView($info_view); diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index c83990cc15..a77b000db9 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -291,6 +291,7 @@ abstract class DiffusionController extends PhabricatorController { return id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle($title) + ->setFlush(true) ->appendChild($body); } @@ -300,6 +301,27 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($pager); } + protected function renderCommitHashTag(DiffusionRequest $drequest) { + $stable_commit = $drequest->getStableCommit(); + $commit = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $stable_commit, + )), + ), + $drequest->getRepository()->formatCommitName($stable_commit, true)); + + $tag = id(new PHUITagView()) + ->setName($commit) + ->setShade('indigo') + ->setType(PHUITagView::TYPE_SHADE); + + return $tag; + } + protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) { $readme_path = $browse->getReadmePath(); if ($readme_path === null) { diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 59248b7acf..eca8fe2a2f 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -40,8 +40,6 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); $show_graph = !strlen($drequest->getPath()); - $content = array(); - $history_table = id(new DiffusionHistoryTableView()) ->setUser($request->getUser()) ->setDiffusionRequest($drequest) @@ -55,23 +53,13 @@ final class DiffusionHistoryController extends DiffusionController { $history_table->setIsTail(!$pager->getHasMorePages()); } - $history_panel = new PHUIObjectBoxView(); - $history_panel->setHeaderText(pht('History')); - $history_panel->setTable($history_table); + $history_header = $this->buildHistoryHeader($drequest); + $history_panel = id(new PHUIObjectBoxView()) + ->setHeader($history_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($history_table); - $content[] = $history_panel; - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setPolicyObject($repository) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')); - - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $header = $this->buildHeader($drequest, $repository); $crumbs = $this->buildCrumbs( array( @@ -79,9 +67,17 @@ final class DiffusionHistoryController extends DiffusionController { 'path' => true, 'view' => 'history', )); + $crumbs->setBorder(true); $pager_box = $this->renderTablePagerBox($pager); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $history_panel, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -91,28 +87,39 @@ final class DiffusionHistoryController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, - $pager_box, + $view, )); } - private function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $tag = $this->renderCommitHashTag($drequest); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag) + ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeaderIcon('fa-clock-o'); + + return $header; + + } + + private function buildHistoryHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); $browse_uri = $drequest->generateURI( array( 'action' => 'browse', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Browse Content')) - ->setHref($browse_uri) - ->setIcon('fa-files-o')); + $browse_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Browse')) + ->setHref($browse_uri) + ->setIcon('fa-files-o'); // TODO: Sometimes we do have a change view, we need to look at the most // recent history entry to figure it out. @@ -130,41 +137,18 @@ final class DiffusionHistoryController extends DiffusionController { ->alter('copies', true); } - $view->addAction( - id(new PhabricatorActionView()) - ->setName($branch_name) - ->setIcon('fa-code-fork') - ->setHref($branch_uri)); + $branch_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($branch_name) + ->setIcon('fa-code-fork') + ->setHref($branch_uri); - return $view; - } + $header = id(new PHUIHeaderView()) + ->setHeader(pht('History')) + ->addActionLink($browse_button) + ->addActionLink($branch_button); - protected function buildPropertyView( - DiffusionRequest $drequest, - PhabricatorActionListView $actions) { - - $viewer = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $stable_commit = $drequest->getStableCommit(); - - $view->addProperty( - pht('Commit'), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $stable_commit, - )), - ), - $drequest->getRepository()->formatCommitName($stable_commit))); - - return $view; + return $header; } } diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index dcbb217d99..fbd059796a 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -157,6 +157,7 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $title = array('Lint'); @@ -179,7 +180,7 @@ final class DiffusionLintController extends DiffusionController { $header = id(new PHUIHeaderView()) ->setHeader($this->renderPathLinks($drequest, 'lint')) ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()); + ->setHeaderIcon('fa-code'); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView( $drequest, @@ -189,18 +190,28 @@ final class DiffusionLintController extends DiffusionController { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } else { $object_box = null; + $header = id(new PHUIHeaderView()) + ->setHeader(pht('All Lint')) + ->setHeaderIcon('fa-code'); } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $object_box, + $content, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, + $view, )); } @@ -444,6 +455,7 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $crumbs = $this->buildCrumbs( @@ -454,6 +466,16 @@ final class DiffusionLintController extends DiffusionController { )); $pager_box = $this->renderTablePagerBox($pager); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName())) + ->setHeaderIcon('fa-code'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $content, + $pager_box, + )); return $this->newPage() ->setTitle( @@ -464,8 +486,7 @@ final class DiffusionLintController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 6504ee50e1..792bcc4249 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -16,11 +16,14 @@ final class DiffusionRepositoryController extends DiffusionController { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $content = array(); - $crumbs = $this->buildCrumbs(); + $crumbs->setBorder(true); - $content[] = $this->buildPropertiesTable($drequest->getRepository()); + $header = $this->buildHeaderView($repository); + $curtain = $this->buildCurtain($repository); + $property_table = $this->buildPropertiesTable($repository); + $description = $this->buildDescriptionView($repository); + $locate_file = $this->buildLocateFile(); // Before we do any work, make sure we're looking at a some content: we're // on a valid branch, and the repository is not empty. @@ -68,14 +71,24 @@ final class DiffusionRepositoryController extends DiffusionController { } if ($page_has_content) { - $content[] = $this->buildNormalContent($drequest); + $content = $this->buildNormalContent($drequest); } else { - $content[] = id(new PHUIInfoView()) + $content = id(new PHUIInfoView()) ->setTitle($empty_title) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors(array($empty_message)); } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $property_table, + $description, + $locate_file, + )) + ->setFooter($content); + return $this->newPage() ->setTitle( array( @@ -83,7 +96,9 @@ final class DiffusionRepositoryController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild(array( + $view, + )); } @@ -206,13 +221,13 @@ final class DiffusionRepositoryController extends DiffusionController { return $content; } - private function buildPropertiesTable(PhabricatorRepository $repository) { - $user = $this->getRequest()->getUser(); - + private function buildHeaderView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()) - ->setUser($user) - ->setPolicyObject($repository); + ->setUser($viewer) + ->setPolicyObject($repository) + ->setHeaderIcon('fa-code'); if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); @@ -227,12 +242,64 @@ final class DiffusionRepositoryController extends DiffusionController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } + return $header; + } - $actions = $this->buildActionList($repository); + private function buildCurtain(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + + $edit_uri = $repository->getPathURI('edit/'); + $curtain = $this->newCurtainView($repository); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Repository')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + if ($repository->isHosted()) { + $push_uri = $this->getApplicationURI( + 'pushlog/?repositories='.$repository->getMonogram()); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Push Logs')) + ->setIcon('fa-list-alt') + ->setHref($push_uri)); + } + + return $curtain; + } + + private function buildDescriptionView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $description = $repository->getDetail('description'); + if (strlen($description)) { + $description = new PHUIRemarkupView($viewer, $description); + $view->addTextContent($description); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DESCRIPTION')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } + return null; + } + + private function buildPropertiesTable(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setObject($repository) - ->setUser($user); + ->setUser($viewer); if ($repository->isHosted()) { $ssh_uri = $repository->getSSHCloneURIObject(); @@ -286,21 +353,10 @@ final class DiffusionRepositoryController extends DiffusionController { } } - $view->invokeWillRenderEvent(); - - $description = $repository->getDetail('description'); - if (strlen($description)) { - $description = new PHUIRemarkupView($user, $description); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - - $view->setActionList($actions); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($view); + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); $info = null; $drequest = $this->getDiffusionRequest(); @@ -344,7 +400,7 @@ final class DiffusionRepositoryController extends DiffusionController { } private function buildBranchListTable(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); if ($drequest->getBranch() === null) { return null; @@ -379,7 +435,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setBranches($branches) ->setCommits($commits); - $panel = new PHUIObjectBoxView(); + $panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = new PHUIHeaderView(); $header->setHeader(pht('Branches')); @@ -388,7 +445,7 @@ final class DiffusionRepositoryController extends DiffusionController { } $button = new PHUIButtonView(); - $button->setText(pht('Show All Branches')); + $button->setText(pht('Show All')); $button->setTag('a'); $button->setIcon('fa-code-fork'); $button->setHref($drequest->generateURI( @@ -404,7 +461,7 @@ final class DiffusionRepositoryController extends DiffusionController { } private function buildTagListTable(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $repository = $drequest->getRepository(); switch ($repository->getVersionControlSystem()) { @@ -469,46 +526,11 @@ final class DiffusionRepositoryController extends DiffusionController { $panel->setHeader($header); $panel->setTable($view); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $panel; } - private function buildActionList(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); - - $edit_uri = $repository->getPathURI('edit/'); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($repository); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Repository')) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setWorkflow(!$can_edit) - ->setDisabled(!$can_edit)); - - if ($repository->isHosted()) { - $push_uri = $this->getApplicationURI( - 'pushlog/?repositories='.$repository->getMonogram()); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Push Logs')) - ->setIcon('fa-list-alt') - ->setHref($push_uri)); - } - - return $view; - } - private function buildHistoryTable( $history_results, $history, @@ -551,7 +573,7 @@ final class DiffusionRepositoryController extends DiffusionController { ->setIcon('fa-list-alt'); $button = id(new PHUIButtonView()) - ->setText(pht('View Full History')) + ->setText(pht('View History')) ->setHref($drequest->generateURI( array( 'action' => 'history', @@ -559,7 +581,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setTag('a') ->setIcon($icon); - $panel = new PHUIObjectBoxView(); + $panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Commits')) ->addActionLink($button); @@ -569,6 +592,46 @@ final class DiffusionRepositoryController extends DiffusionController { return $panel; } + private function buildLocateFile() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $locate_panel = null; + if ($repository->canUsePathTree()) { + Javelin::initBehavior( + 'diffusion-locate-file', + array( + 'controlID' => 'locate-control', + 'inputID' => 'locate-input', + 'browseBaseURI' => (string)$drequest->generateURI( + array( + 'action' => 'browse', + )), + 'uri' => (string)$drequest->generateURI( + array( + 'action' => 'pathtree', + )), + )); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTypeaheadControl()) + ->setHardpointID('locate-control') + ->setID('locate-input') + ->setLabel(pht('Locate File'))); + $form_box = id(new PHUIBoxView()) + ->appendChild($form->buildLayoutView()); + $locate_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Locate File')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($form_box); + } + return $locate_panel; + } + private function buildBrowseTable( $browse_results, $browse_paths, @@ -606,9 +669,10 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_uri = $drequest->generateURI(array('action' => 'browse')); - $browse_panel = new PHUIObjectBoxView(); + $browse_panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Repository')); + ->setHeader($repository->getName()); $icon = id(new PHUIIconView()) ->setIcon('fa-folder-open'); @@ -621,38 +685,6 @@ final class DiffusionRepositoryController extends DiffusionController { $header->addActionLink($button); $browse_panel->setHeader($header); - - $locate_panel = null; - if ($repository->canUsePathTree()) { - Javelin::initBehavior( - 'diffusion-locate-file', - array( - 'controlID' => 'locate-control', - 'inputID' => 'locate-input', - 'browseBaseURI' => (string)$drequest->generateURI( - array( - 'action' => 'browse', - )), - 'uri' => (string)$drequest->generateURI( - array( - 'action' => 'pathtree', - )), - )); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTypeaheadControl()) - ->setHardpointID('locate-control') - ->setID('locate-input') - ->setLabel(pht('Locate File'))); - $form_box = id(new PHUIBoxView()) - ->appendChild($form->buildLayoutView()); - $locate_panel = id(new PHUIObjectBoxView()) - ->setHeaderText('Locate File') - ->appendChild($form_box); - } - $browse_panel->setTable($browse_table); $pager->setURI($browse_uri, 'offset'); @@ -664,7 +696,6 @@ final class DiffusionRepositoryController extends DiffusionController { } return array( - $locate_panel, $browse_panel, $pager_box, ); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index a9113fee13..ae28bf3993 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -262,10 +262,26 @@ final class DiffusionRepositoryCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form); + ->appendChild($view); + } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php index 8459498374..b8f95f36fc 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php @@ -53,6 +53,10 @@ final class DiffusionRepositoryEditActionsController $title = pht('Edit Actions (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($repository) @@ -97,13 +101,21 @@ final class DiffusionRepositoryEditActionsController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); + } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php index e47bde3902..038cc0f0cd 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php @@ -49,6 +49,10 @@ final class DiffusionRepositoryEditAutomationController $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendRemarkupInstructions( @@ -69,14 +73,21 @@ final class DiffusionRepositoryEditAutomationController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Automation')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index e23a57e655..10f4b13ef9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -105,6 +105,10 @@ final class DiffusionRepositoryEditBasicController $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( @@ -144,16 +148,23 @@ final class DiffusionRepositoryEditBasicController ->appendChild(id(new PHUIFormDividerControl())) ->appendRemarkupInstructions($this->getReadmeInstructions()); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Basic Information')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getReadmeInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php index c256336ec2..927af295d4 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php @@ -98,6 +98,9 @@ final class DiffusionRepositoryEditBranchesController $crumbs->addTextCrumb(pht('Edit Branches')); $title = pht('Edit Branches (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -213,14 +216,21 @@ final class DiffusionRepositoryEditBranchesController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Branches')) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); } private function processBranches($string) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 1e3cb553da..714568f187 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -20,6 +20,7 @@ abstract class DiffusionRepositoryEditController $crumbs->addTextCrumb(pht('Edit'), $edit_uri); } } + $crumbs->setBorder(true); return $crumbs; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php index 0059c9d6c0..256e69ac59 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php @@ -50,6 +50,9 @@ final class DiffusionRepositoryEditEncodingController $crumbs->addTextCrumb(pht('Edit Encoding')); $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($user) @@ -65,15 +68,22 @@ final class DiffusionRepositoryEditEncodingController ->setValue(pht('Save Encoding')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Encoding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getEncodingInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php index be40b794b3..0c759eba59 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php @@ -57,6 +57,9 @@ final class DiffusionRepositoryEditHostingController $crumbs->addTextCrumb(pht('Edit Hosting')); $title = pht('Edit Hosting (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $hosted_control = id(new AphrontFormRadioButtonControl()) ->setName('hosting') @@ -95,14 +98,21 @@ final class DiffusionRepositoryEditHostingController ->setValue(pht('Save and Continue')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Hosting')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } public function handleProtocols(PhabricatorRepository $repository) { @@ -155,7 +165,9 @@ final class DiffusionRepositoryEditHostingController $crumbs->addTextCrumb(pht('Edit Protocols')); $title = pht('Edit Protocols (%s)', $repository->getName()); - + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $rw_message = pht( 'Phabricator will serve a read-write copy of this repository.'); @@ -256,14 +268,21 @@ final class DiffusionRepositoryEditHostingController ->setValue(pht('Save Changes')) ->addCancelButton($prev_uri, pht('Back'))); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Protocols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 0ec8cdd6b8..d8a4cf40ba 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -38,16 +38,16 @@ final class DiffusionRepositoryEditMainController $title = pht('Edit %s', $repository->getName()); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); if ($repository->isTracked()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'dark', pht('Inactive')); } - $basic_actions = $this->buildBasicActions($repository); - $basic_properties = - $this->buildBasicProperties($repository, $basic_actions); + $curtain = $this->buildCurtain($repository); + $basic_properties = $this->buildBasicProperties($repository); $policy_actions = $this->buildPolicyActions($repository); $policy_properties = @@ -119,16 +119,14 @@ final class DiffusionRepositoryEditMainController $boxes = array(); - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($basic_properties); - $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Policies')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($policy_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Hosting')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($hosting_properties); if ($repository->canMirror()) { @@ -156,6 +154,7 @@ final class DiffusionRepositoryEditMainController $boxes[] = id(new PHUIObjectBoxView()) ->setFormErrors($mirror_info) ->setHeaderText(pht('Mirrors')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($mirror_properties); $boxes[] = $mirror_list; @@ -164,73 +163,88 @@ final class DiffusionRepositoryEditMainController if ($remote_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remote')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($remote_properties); } if ($storage_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Storage')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($storage_properties); } if ($staging_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Staging')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($staging_properties); } if ($automation_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Automation')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($automation_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($encoding_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Symbols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($symbols_properties); if ($branches_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Branches')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($branches_properties); } if ($subversion_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Subversion')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($subversion_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($actions_properties); - return $this->buildApplicationPage( - array( - $crumbs, + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $basic_properties) + ->setMainColumn(array( $boxes, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } - private function buildBasicActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($repository); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Basic Information')) ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')); - $view->addAction($edit); + $curtain->addAction($edit); $edit = id(new PhabricatorActionView()) ->setIcon('fa-refresh') @@ -238,7 +252,7 @@ final class DiffusionRepositoryEditMainController ->setWorkflow(true) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/update/')); - $view->addAction($edit); + $curtain->addAction($edit); $activate = id(new PhabricatorActionView()) ->setHref( @@ -255,9 +269,9 @@ final class DiffusionRepositoryEditMainController ->setName(pht('Activate Repository')); } - $view->addAction($activate); + $curtain->addAction($activate); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setIcon('fa-times') @@ -266,19 +280,16 @@ final class DiffusionRepositoryEditMainController ->setDisabled(true) ->setWorkflow(true)); - return $view; + return $curtain; } private function buildBasicProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { + PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($repository) - ->setActionList($actions); + ->setUser($viewer); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); @@ -322,7 +333,7 @@ final class DiffusionRepositoryEditMainController } private function buildEncodingActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -341,7 +352,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -358,7 +369,7 @@ final class DiffusionRepositoryEditMainController } private function buildPolicyActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -377,7 +388,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -412,7 +423,7 @@ final class DiffusionRepositoryEditMainController } private function buildBranchesActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -431,7 +442,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -461,7 +472,7 @@ final class DiffusionRepositoryEditMainController } private function buildSubversionActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -480,7 +491,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -500,7 +511,7 @@ final class DiffusionRepositoryEditMainController } private function buildActionsActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -519,7 +530,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -541,7 +552,7 @@ final class DiffusionRepositoryEditMainController } private function buildRemoteActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -560,7 +571,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -581,7 +592,7 @@ final class DiffusionRepositoryEditMainController } private function buildStorageActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -600,7 +611,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -801,7 +812,7 @@ final class DiffusionRepositoryEditMainController private function buildRepositoryStatus( PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $is_cluster = $repository->getAlmanacServicePHID(); $view = new PHUIStatusListView(); @@ -1188,7 +1199,7 @@ final class DiffusionRepositoryEditMainController private function buildMirrorActions( PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $mirror_actions = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -1211,7 +1222,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $mirror_properties = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -1262,11 +1273,14 @@ final class DiffusionRepositoryEditMainController $mirror_list->addItem($item); } - return $mirror_list; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Configured Mirrors')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($mirror_list); } private function buildSymbolsActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -1285,7 +1299,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php index deb8fad669..2d158e61ea 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -45,7 +45,10 @@ final class DiffusionRepositoryEditStagingController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Staging')); - $title = pht('Edit %s', $repository->getName()); + $title = pht('Edit Staging (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -68,14 +71,21 @@ final class DiffusionRepositoryEditStagingController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Staging')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index 711844188a..f3a492e3b3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -22,6 +22,9 @@ final class DiffusionRepositoryEditStorageController $crumbs->addTextCrumb(pht('Edit Storage')); $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $service_phid = $repository->getAlmanacServicePHID(); if ($service_phid) { @@ -57,15 +60,21 @@ final class DiffusionRepositoryEditStorageController id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri, pht('Done'))); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form) - ->setFormErrors($errors); + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Storage')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php index 93b9193f14..6fcc316135 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php @@ -63,6 +63,9 @@ final class DiffusionRepositoryEditSubversionController $crumbs->addTextCrumb(pht('Edit Subversion Info')); $title = pht('Edit Subversion Info (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -96,13 +99,20 @@ final class DiffusionRepositoryEditSubversionController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Subversion')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php index 6076d2df65..06ce23032a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php @@ -59,7 +59,10 @@ final class DiffusionRepositorySymbolsController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Symbols')); - $title = pht('Edit %s', $repository->getName()); + $title = pht('Edit Symbols (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -85,15 +88,22 @@ final class DiffusionRepositorySymbolsController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Symbols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php index defe09d58c..3aaccffd22 100644 --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -134,17 +134,24 @@ final class DiffusionSymbolController extends DiffusionController { $table->setNoDataString( pht('No matching symbol could be found in any indexed repository.')); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Similar Symbols')) - ->setTable($table); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Similar Symbols')) + ->setHeaderIcon('fa-bullseye'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Find Symbol')); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $table, + )); return $this->newPage() ->setTitle(pht('Find Symbol')) ->setCrumbs($crumbs) - ->appendChild($panel); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 4749675bbb..5abd93c37d 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -45,6 +45,11 @@ final class DiffusionTagListController extends DiffusionController { $tags = $pager->sliceResults($tags); $content = null; + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Tags')) + ->setHeaderIcon('fa-tags'); + if (!$tags) { $content = $this->renderStatusMessage( pht('No Tags'), @@ -69,11 +74,7 @@ final class DiffusionTagListController extends DiffusionController { $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Tags')) - ->appendChild($view); - - $content = $panel; + $content = $view; } $crumbs = $this->buildCrumbs( @@ -81,9 +82,22 @@ final class DiffusionTagListController extends DiffusionController { 'tags' => true, 'commit' => $drequest->getSymbolicCommit(), )); + $crumbs->setBorder(true); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getDisplayName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($view); $pager_box = $this->renderTablePagerBox($pager); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -91,11 +105,7 @@ final class DiffusionTagListController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild( - array( - $content, - $pager_box, - )); + ->appendChild($view); } } diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index 6354c5dc5e..fa5342ad9d 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -90,7 +90,6 @@ final class DiffusionBrowseTableView extends DiffusionView { $browse_link, idx($dict, 'lint'), $dict['commit'], - $dict['author'], $dict['details'], $dict['date'], ); @@ -120,7 +119,6 @@ final class DiffusionBrowseTableView extends DiffusionView { pht('Path'), ($lint ? $lint : pht('Lint')), pht('Modified'), - pht('Author/Committer'), pht('Details'), pht('Committed'), )); @@ -130,7 +128,6 @@ final class DiffusionBrowseTableView extends DiffusionView { '', '', '', - '', 'wide', '', )); @@ -142,7 +139,6 @@ final class DiffusionBrowseTableView extends DiffusionView { true, true, true, - true, )); $view->setDeviceVisibility( @@ -150,7 +146,6 @@ final class DiffusionBrowseTableView extends DiffusionView { true, true, false, - true, false, true, false, diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 331c172866..5aa1fad33c 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -95,7 +95,9 @@ abstract class DiffusionView extends AphrontView { } $icon = DifferentialChangeType::getIconForFileType($file_type); - $icon_view = id(new PHUIIconView())->setIcon($icon); + $color = DifferentialChangeType::getIconColorForFileType($file_type); + $icon_view = id(new PHUIIconView()) + ->setIcon($icon.' '.$color); // If we're rendering a file or directory name, don't show the tooltip. if ($display_name !== null) { diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index 0da471574c..92481bc614 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -14,6 +14,7 @@ final class PHUIInfoView extends AphrontView { private $id; private $buttons = array(); private $isHidden; + private $flush; public function setTitle($title) { $this->title = $title; @@ -40,6 +41,11 @@ final class PHUIInfoView extends AphrontView { return $this; } + public function setFlush($flush) { + $this->flush = $flush; + return $this; + } + public function addButton(PHUIButtonView $button) { $this->buttons[] = $button; return $this; @@ -87,6 +93,9 @@ final class PHUIInfoView extends AphrontView { $classes[] = 'phui-info-view'; $classes[] = 'phui-info-severity-'.$this->severity; $classes[] = 'grouped'; + if ($this->flush) { + $classes[] = 'phui-info-view-flush'; + } $classes = implode(' ', $classes); $children = $this->renderChildren(); diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php index b5f6ad7066..cfd18add58 100644 --- a/src/view/form/PHUIPagedFormView.php +++ b/src/view/form/PHUIPagedFormView.php @@ -263,6 +263,7 @@ final class PHUIPagedFormView extends AphrontView { $form->appendChild($submit); $box = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setFormErrors($errors) ->setForm($form); diff --git a/src/view/phui/PHUIHeadThingView.php b/src/view/phui/PHUIHeadThingView.php index 4ff0290578..dd788399d4 100644 --- a/src/view/phui/PHUIHeadThingView.php +++ b/src/view/phui/PHUIHeadThingView.php @@ -35,6 +35,9 @@ final class PHUIHeadThingView extends AphrontTagView { $classes = array(); $classes[] = 'phui-head-thing-view'; + if ($this->image) { + $classes[] = 'phui-head-has-image'; + } if ($this->size) { $classes[] = $this->size; @@ -57,8 +60,11 @@ final class PHUIHeadThingView extends AphrontTagView { 'href' => $this->imageHref, )); - return array($image, $this->content); - + if ($this->image) { + return array($image, $this->content); + } else { + return $this->content; + } } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index 087ce9362e..c735e3d842 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -20,3 +20,11 @@ input.diffusion-clone-uri { .diffusion-link-icon + .diffusion-link-icon { margin-left: 6px; } + +.diffusion-search-boxen { + padding: 16px; +} + +.diffusion-search-boxen .phui-form-view { + padding: 0; +} diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index ca52d53a79..7f404e3b9b 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -4,7 +4,6 @@ .diffusion-source { width: 100%; - font: 10px/13px "Menlo", "Consolas", "Monaco", monospace; background: #fff; } @@ -21,13 +20,12 @@ } .diffusion-source td { - letter-spacing: 0.0083334px; - vertical-align: top; - white-space: pre-wrap; - padding-bottom: 1px; - padding-left: 8px; - line-height: 16px; - width: 100%; + vertical-align: top; + white-space: pre-wrap; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 8px; + width: 100%; } .diffusion-browse-type-form { diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index ea825db3be..35f5067fec 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -86,8 +86,10 @@ padding: 6px 16px; } -.device .phui-box.phui-box-blue-property .phui-header-shell { - padding: 6px 12px; +.device .phui-box.phui-box-blue-property .phui-header-shell, +.device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed + .phui-header-shell { + padding: 6px 12px; } .phui-box.phui-box-blue-property .phui-header-header { @@ -98,3 +100,16 @@ .phui-box-blue-property .phui-object-item-list-view.phui-object-list-flush { padding: 2px 8px; } + +body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed { + padding: 0; +} + +body .phui-box-blue-property .phui-header-shell + .phui-object-box { + margin-bottom: 0; +} + +.phui-box-blue-property .phui-header-shell + .phui-object-box + .phui-header-shell { + background: #fff; +} diff --git a/webroot/rsrc/css/phui/phui-head-thing.css b/webroot/rsrc/css/phui/phui-head-thing.css index b82c9321a3..bf491c4f1f 100644 --- a/webroot/rsrc/css/phui/phui-head-thing.css +++ b/webroot/rsrc/css/phui/phui-head-thing.css @@ -6,6 +6,9 @@ height: 24px; line-height: 22px; color: {$greytext}; +} + +.phui-head-thing-view.phui-head-has-image { position: relative; padding-left: 32px; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index a61a73fc88..2a1731eb61 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -324,3 +324,7 @@ body .phui-header-shell.phui-bleed-header .phui-profile-header .phui-header-col3 { vertical-align: top; } + +.phui-header-view .phui-tag-shade-indigo a { + color: {$sh-indigotext}; +} diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 59405995a5..d3b12dd4fe 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -10,6 +10,10 @@ border-radius: 3px; } +.phui-info-view.phui-info-view-flush { + margin: 0 0 20px 0; +} + .device .phui-info-view { margin: 8px; } diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css index 52e3ee0944..ab250a3cc5 100644 --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -199,7 +199,6 @@ .phui-property-list-image-content img { margin: 20px auto; background: url('/rsrc/image/checker_light.png'); - border: 1px solid {$lightblueborder}; } .device-desktop .phui-property-list-image-content img:hover { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index a4ee2c4d3f..1496a3b0f2 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -79,10 +79,6 @@ margin: 0 0 20px 0; } -.phui-two-column-view .phui-object-box.phui-object-box-collapsed { - padding: 0; -} - /* Timeline */ .phui-two-column-view .phui-timeline-view { @@ -115,12 +111,12 @@ .device-desktop .phui-main-column .phui-property-list-key { margin-left: 0; - width: 140px; + width: 160px; } .device-desktop .phui-main-column .phui-property-list-value { margin-left: 8px; - width: calc(100% - 180px); + width: calc(100% - 200px); } @@ -132,13 +128,18 @@ } .device-desktop .phui-two-column-view .phui-property-list-container { - padding: 12px 16px; + padding: 16px 0; } .device .phui-two-column-view .phui-property-list-container { padding: 12px 8px; } +.phui-two-column-view .phui-property-list-container + .keyboard-shortcuts-available { + display: none; +} + .phui-two-column-properties.phui-object-box { border: 1px solid rgba({$alphablue}, .2); } @@ -191,3 +192,7 @@ .phui-info-view { margin: 0; } + +.phui-two-column-view .phui-object-box .phui-header-shell + .phui-info-view { + margin: 16px 16px 0 16px; +} From 2b9d4f70ba3a073e978b2fbd4d13466822bb6e4f Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Thu, 17 Mar 2016 20:24:03 +0000 Subject: [PATCH 17/25] Remarkup rule for rendering PHIDs as handles Summary: adds the `{{PHID....}}` rule. Should mostly be useful in UI code that refers to Objects. It doesn't add any mention links/transactions. Test Plan: Comment with this, see email (plain + html) and comment box. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15488 --- src/__phutil_library_map__.php | 2 + .../PhabricatorHandleRemarkupRule.php | 109 ++++++++++++++++++ .../markup/PhabricatorMarkupEngine.php | 1 + 3 files changed, 112 insertions(+) create mode 100644 src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 85400a5ebc..9f499bc955 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2454,6 +2454,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php', 'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php', 'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php', + 'PhabricatorHandleRemarkupRule' => 'applications/phid/remarkup/PhabricatorHandleRemarkupRule.php', 'PhabricatorHandlesEditField' => 'applications/transactions/editfield/PhabricatorHandlesEditField.php', 'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php', 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php', @@ -6897,6 +6898,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'Phobject', 'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase', 'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorHandleRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorHandlesEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorHarbormasterApplication' => 'PhabricatorApplication', 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php b/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php new file mode 100644 index 0000000000..35920e16e0 --- /dev/null +++ b/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php @@ -0,0 +1,109 @@ +getEngine(); + $viewer = $engine->getConfig('viewer'); + + if (!$this->isFlatText($matches[0])) { + return $matches[0]; + } + + $phid_type = phid_get_type($matches[1]); + if ($phid_type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { + return $matches[0]; + } + + $token = $engine->storeText($matches[0]); + if ($engine->isTextMode()) { + return $token; + } + + $original_key = self::KEY_RULE_HANDLE_ORIGINAL; + $original = $engine->getTextMetadata($original_key, array()); + $original[$token] = $matches[0]; + $engine->setTextMetadata($original_key, $original); + + $metadata_key = self::KEY_RULE_HANDLE; + $metadata = $engine->getTextMetadata($metadata_key, array()); + $phid = $matches[1]; + if (empty($metadata[$phid])) { + $metadata[$phid] = array(); + } + $metadata[$phid][] = $token; + $engine->setTextMetadata($metadata_key, $metadata); + + return $token; + } + + public function didMarkupText() { + $engine = $this->getEngine(); + + $metadata_key = self::KEY_RULE_HANDLE; + + $metadata = $engine->getTextMetadata($metadata_key, array()); + if (empty($metadata)) { + // No mentions, or we already processed them. + return; + } + + $original_key = self::KEY_RULE_HANDLE_ORIGINAL; + $original = $engine->getTextMetadata($original_key, array()); + + $phids = array_keys($metadata); + + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getEngine()->getConfig('viewer')) + ->withPHIDs($phids) + ->execute(); + + foreach ($metadata as $phid => $tokens) { + $handle = idx($handles, $phid); + + if ($handle->isComplete()) { + if ($engine->isHTMLMailMode()) { + $href = $handle->getURI(); + $href = PhabricatorEnv::getProductionURI($href); + + $link = phutil_tag( + 'a', + array( + 'href' => $href, + 'style' => ' + border-color: #f1f7ff; + color: #19558d; + background-color: #f1f7ff; + border: 1px solid transparent; + border-radius: 3px; + font-weight: bold; + padding: 0 4px;', + ), + $handle->getLinkName()); + } else { + $link = $handle->renderTag(); + $link->setPHID($phid); + } + foreach ($tokens as $token) { + $engine->overwriteStoredText($token, $link); + } + } else { + foreach ($tokens as $token) { + $engine->overwriteStoredText($token, idx($original, $token)); + } + } + } + + $engine->setTextMetadata($metadata_key, array()); + } +} diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index c3d9dab02a..f32971646a 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -494,6 +494,7 @@ final class PhabricatorMarkupEngine extends Phobject { $rules[] = new PhabricatorIconRemarkupRule(); $rules[] = new PhabricatorEmojiRemarkupRule(); + $rules[] = new PhabricatorHandleRemarkupRule(); $applications = PhabricatorApplication::getAllInstalledApplications(); foreach ($applications as $application) { From 76bfd91fd071a002b9a3cf532e0a3b08246df92a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 17 Mar 2016 13:32:22 -0700 Subject: [PATCH 18/25] Add icons/colors to Audit transactions Summary: Fixes T10616. Adds additional colors, icons, to Audit transactions Test Plan: Mess with various Audit states. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10616 Differential Revision: https://secure.phabricator.com/D15490 --- .../storage/PhabricatorAuditTransaction.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php index 20203a6f68..e1f7a9d08a 100644 --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -117,12 +117,37 @@ final class PhabricatorAuditTransaction return 'red'; case PhabricatorAuditActionConstants::ACCEPT: return 'green'; + case PhabricatorAuditActionConstants::RESIGN: + return 'black'; + case PhabricatorAuditActionConstants::CLOSE: + return 'indigo'; } } return parent::getColor(); } + public function getIcon() { + + $type = $this->getTransactionType(); + + switch ($type) { + case PhabricatorAuditActionConstants::ACTION: + switch ($this->getNewValue()) { + case PhabricatorAuditActionConstants::CONCERN: + return 'fa-exclamation-circle'; + case PhabricatorAuditActionConstants::ACCEPT: + return 'fa-check'; + case PhabricatorAuditActionConstants::RESIGN: + return 'fa-plane'; + case PhabricatorAuditActionConstants::CLOSE: + return 'fa-check'; + } + } + + return parent::getIcon(); + } + public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); From f46686ff586d7eb2091fd3020bfe4cb81bac15a9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Mar 2016 12:49:18 -0700 Subject: [PATCH 19/25] Implement a Git LFS link table and basic batch API Summary: Ref T7789. This implements: - A new table to store the `` relationship between Git LFS files and Phabricator file objects. - A basic response to `batch` commands, which return actions for a list of files. Test Plan: Ran `git lfs push origin master`, got a little further than previously: ``` epriestley@orbital ~/dev/scratch/poemslocal $ git lfs push origin master Git LFS: (2 of 1 files) 174.24 KB / 87.12 KB Git LFS operation "upload/b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69" is not supported by this server. Git LFS operation "upload/b7e0aeb82a03d627c6aa5fc1bbfd454b6789d9d9affc8607d40168fa18cf6c69" is not supported by this server. ``` With `GIT_TRACE=1`, this shows the batch part of the API going through. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D15489 --- .../sql/autopatches/20160317.lfs.01.ref.sql | 11 ++ src/__phutil_library_map__.php | 7 + .../controller/DiffusionServeController.php | 145 +++++++++++++++++- .../DiffusionGitLFSAuthenticateWorkflow.php | 24 +-- .../DiffusionGitLFSTemporaryTokenType.php | 24 +++ .../PhabricatorRepositoryGitLFSRefQuery.php | 64 ++++++++ .../PhabricatorRepositoryGitLFSRef.php | 51 ++++++ 7 files changed, 304 insertions(+), 22 deletions(-) create mode 100644 resources/sql/autopatches/20160317.lfs.01.ref.sql create mode 100644 src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php create mode 100644 src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php diff --git a/resources/sql/autopatches/20160317.lfs.01.ref.sql b/resources/sql/autopatches/20160317.lfs.01.ref.sql new file mode 100644 index 0000000000..18c45c525a --- /dev/null +++ b/resources/sql/autopatches/20160317.lfs.01.ref.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_gitlfsref ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + repositoryPHID VARBINARY(64) NOT NULL, + objectHash BINARY(64) NOT NULL, + byteSize BIGINT UNSIGNED NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + filePHID VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_hash` (repositoryPHID, objectHash) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9f499bc955..38f9505b24 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3100,6 +3100,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', + 'PhabricatorRepositoryGitLFSRef' => 'applications/repository/storage/PhabricatorRepositoryGitLFSRef.php', + 'PhabricatorRepositoryGitLFSRefQuery' => 'applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php', 'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php', 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', @@ -7665,6 +7667,11 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'Phobject', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', + 'PhabricatorRepositoryGitLFSRef' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorRepositoryGitLFSRefQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryGraphCache' => 'Phobject', 'PhabricatorRepositoryGraphStream' => 'Phobject', 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index fcc260b287..9fca803008 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -9,6 +9,8 @@ final class DiffusionServeController extends DiffusionController { private $gitLFSToken; public function setServiceViewer(PhabricatorUser $viewer) { + $this->getRequest()->setUser($viewer); + $this->serviceViewer = $viewer; return $this; } @@ -42,6 +44,7 @@ final class DiffusionServeController extends DiffusionController { $content_type = $request->getHTTPHeader('Content-Type'); $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); + $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); // This may have a "charset" suffix, so only match the prefix. $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))'; @@ -64,6 +67,10 @@ final class DiffusionServeController extends DiffusionController { // This is a Git LFS HTTP API request. $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $this->isGitLFSRequest = true; + } else if ($request_type == 'git-lfs') { + // This is a Git LFS object content request. + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $this->isGitLFSRequest = true; } else if ($request->getExists('cmd')) { // Mercurial also sends an Accept header like // "application/mercurial-0.1", and a User-Agent like @@ -884,12 +891,140 @@ final class DiffusionServeController extends DiffusionController { } $path = $this->getGitLFSRequestPath($repository); + if ($path == 'objects/batch') { + return $this->serveGitLFSBatchRequest($repository, $viewer); + } else { + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS operation "%s" is not supported by this server.', + $path)); + } + } - return DiffusionGitLFSResponse::newErrorResponse( - 404, - pht( - 'Git LFS operation "%s" is not supported by this server.', - $path)); + private function serveGitLFSBatchRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + + $input = PhabricatorStartup::getRawInput(); + $input = phutil_json_decode($input); + + $operation = idx($input, 'operation'); + switch ($operation) { + case 'upload': + $want_upload = true; + break; + case 'download': + $want_upload = false; + break; + default: + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS batch operation "%s" is not supported by this server.', + $operation)); + } + + $objects = idx($input, 'objects', array()); + + $hashes = array(); + foreach ($objects as $object) { + $hashes[] = idx($object, 'oid'); + } + + if ($hashes) { + $refs = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes($hashes) + ->execute(); + $refs = mpull($refs, null, 'getObjectHash'); + } else { + $refs = array(); + } + + $file_phids = mpull($refs, 'getFilePHID'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phids)) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + $authorization = null; + $output = array(); + foreach ($objects as $object) { + $oid = idx($object, 'oid'); + $size = idx($object, 'size'); + $ref = idx($refs, $oid); + + // NOTE: If we already have a ref for this object, we only emit a + // "download" action. The client should not upload the file again. + + $actions = array(); + if ($ref) { + $file = idx($files, $ref->getFilePHID()); + if ($file) { + $get_uri = $file->getCDNURIWithToken(); + $actions['download'] = array( + 'href' => $get_uri, + ); + } + } else if ($want_upload) { + if (!$authorization) { + // Here, we could reuse the existing authorization if we have one, + // but it's a little simpler to just generate a new one + // unconditionally. + $authorization = $this->newGitLFSHTTPAuthorization( + $repository, + $viewer, + $operation); + } + + $put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}"); + + $actions['upload'] = array( + 'href' => $put_uri, + 'header' => array( + 'Authorization' => $authorization, + 'X-Phabricator-Request-Type' => 'git-lfs', + ), + ); + } + + $output[] = array( + 'oid' => $oid, + 'size' => $size, + 'actions' => $actions, + ); + } + + $output = array( + 'objects' => $output, + ); + + return id(new DiffusionGitLFSResponse()) + ->setContent($output); + } + + private function newGitLFSHTTPAuthorization( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $operation) { + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( + $repository, + $viewer, + $operation); + + unset($unguarded); + + return $authorization; } private function getGitLFSRequestPath(PhabricatorRepository $repository) { diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php index aa46a9e2c2..362a4887ec 100644 --- a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php @@ -83,25 +83,15 @@ final class DiffusionGitLFSAuthenticateWorkflow // on this host, and does not require the user to have a VCS password. $user = $this->getUser(); - $headers = array(); - $lfs_user = DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME; - $lfs_pass = Filesystem::readRandomCharacters(32); - $lfs_hash = PhabricatorHash::digest($lfs_pass); + $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( + $repository, + $user, + $operation); - $ttl = PhabricatorTime::getNow() + phutil_units('1 day in seconds'); - - $token = id(new PhabricatorAuthTemporaryToken()) - ->setTokenResource($repository->getPHID()) - ->setTokenType(DiffusionGitLFSTemporaryTokenType::TOKENTYPE) - ->setTokenCode($lfs_hash) - ->setUserPHID($user->getPHID()) - ->setTemporaryTokenProperty('lfs.operation', $operation) - ->setTokenExpires($ttl) - ->save(); - - $authorization_header = base64_encode($lfs_user.':'.$lfs_pass); - $headers['Authorization'] = 'Basic '.$authorization_header; + $headers = array( + 'authorization' => $authorization, + ); $result = array( 'header' => $headers, diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php index f7cf558c36..0973072ba4 100644 --- a/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php @@ -15,4 +15,28 @@ final class DiffusionGitLFSTemporaryTokenType return pht('Git LFS Token'); } + public static function newHTTPAuthorization( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $operation) { + + $lfs_user = self::HTTP_USERNAME; + $lfs_pass = Filesystem::readRandomCharacters(32); + $lfs_hash = PhabricatorHash::digest($lfs_pass); + + $ttl = PhabricatorTime::getNow() + phutil_units('1 day in seconds'); + + $token = id(new PhabricatorAuthTemporaryToken()) + ->setTokenResource($repository->getPHID()) + ->setTokenType(self::TOKENTYPE) + ->setTokenCode($lfs_hash) + ->setUserPHID($viewer->getPHID()) + ->setTemporaryTokenProperty('lfs.operation', $operation) + ->setTokenExpires($ttl) + ->save(); + + $authorization_header = base64_encode($lfs_user.':'.$lfs_pass); + return 'Basic '.$authorization_header; + } + } diff --git a/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php new file mode 100644 index 0000000000..cd693be605 --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php @@ -0,0 +1,64 @@ +ids = $ids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + + public function withObjectHashes(array $hashes) { + $this->objectHashes = $hashes; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositoryGitLFSRef(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->objectHashes !== null) { + $where[] = qsprintf( + $conn, + 'objectHash IN (%Ls)', + $this->objectHashes); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php new file mode 100644 index 0000000000..c049b4897a --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php @@ -0,0 +1,51 @@ + array( + 'objectHash' => 'bytes64', + 'byteSize' => 'uint64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_hash' => array( + 'columns' => array('repositoryPHID', 'objectHash'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +} From f07d0ae7c3aee1ca21567f95360acda205950edc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 18 Mar 2016 08:59:21 -0700 Subject: [PATCH 20/25] Make dates/times more concise in Diffusion Summary: I think I like this better -- but maybe right-aligned? Test Plan: {F1180295} {F1180296} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15495 --- .../DiffusionLastModifiedController.php | 2 +- .../view/DiffusionBranchTableView.php | 4 +-- .../view/DiffusionBrowseTableView.php | 2 +- .../view/DiffusionHistoryTableView.php | 4 +-- .../view/DiffusionPushLogListView.php | 4 +-- .../diffusion/view/DiffusionTagListView.php | 4 ++- .../people/storage/PhabricatorUser.php | 32 +++++++++++++++++++ 7 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index 0fea238249..8a4debfcea 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -103,7 +103,7 @@ final class DiffusionLastModifiedController extends DiffusionController { $modified = DiffusionView::linkCommit( $drequest->getRepository(), $commit->getCommitIdentifier()); - $date = phabricator_datetime($epoch, $viewer); + $date = $viewer->formatShortDateTime($epoch); } else { $modified = ''; $date = ''; diff --git a/src/applications/diffusion/view/DiffusionBranchTableView.php b/src/applications/diffusion/view/DiffusionBranchTableView.php index 462f296bcb..b75228a0ce 100644 --- a/src/applications/diffusion/view/DiffusionBranchTableView.php +++ b/src/applications/diffusion/view/DiffusionBranchTableView.php @@ -39,7 +39,7 @@ final class DiffusionBranchTableView extends DiffusionView { $commit = idx($commits, $branch->getCommitIdentifier()); if ($commit) { $details = $commit->getSummary(); - $datetime = phabricator_datetime($commit->getEpoch(), $viewer); + $datetime = $viewer->formatShortDateTime($commit->getEpoch()); $buildable = idx($buildables, $commit->getPHID()); if ($buildable) { $build_status = $this->renderBuildable($buildable); @@ -147,7 +147,7 @@ final class DiffusionBranchTableView extends DiffusionView { '', 'wide', '', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index fa5342ad9d..ffc5ce61ed 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -129,7 +129,7 @@ final class DiffusionBrowseTableView extends DiffusionView { '', '', 'wide', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index e62c4382c9..a9fb34e683 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -95,7 +95,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $epoch = $history->getEpoch(); if ($epoch) { - $committed = phabricator_datetime($epoch, $viewer); + $committed = $viewer->formatShortDateTime($epoch); } else { $committed = null; } @@ -195,7 +195,7 @@ final class DiffusionHistoryTableView extends DiffusionView { '', '', 'wide', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index 7cc02a49b4..73a44794e8 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -88,7 +88,7 @@ final class DiffusionPushLogListView extends AphrontView { // TODO: Make these human-readable. $log->getChangeFlags(), $log->getPushEvent()->getRejectCode(), - phabricator_datetime($log->getEpoch(), $viewer), + $viewer->formatShortDateTime($log->getEpoch()), ); } @@ -119,7 +119,7 @@ final class DiffusionPushLogListView extends AphrontView { 'wide', 'n', 'n', - 'date', + 'right', )); return $table; diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index 923fa30fc5..df59925522 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -28,6 +28,7 @@ final class DiffusionTagListView extends DiffusionView { public function render() { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $viewer = $this->getViewer(); $buildables = $this->loadBuildables($this->commits); $has_builds = false; @@ -100,7 +101,7 @@ final class DiffusionTagListView extends DiffusionView { $build, $author, $description, - phabricator_datetime($tag->getEpoch(), $this->getViewer()), + $viewer->formatShortDateTime($tag->getEpoch()), ); } @@ -123,6 +124,7 @@ final class DiffusionTagListView extends DiffusionView { '', '', 'wide', + 'right', )) ->setColumnVisibility( array( diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 07c5695945..ed6fceba1e 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -742,6 +742,38 @@ final class PhabricatorUser return new DateTimeZone($this->getTimezoneIdentifier()); } + public function formatShortDateTime($when, $now = null) { + if ($now === null) { + $now = PhabricatorTime::getNow(); + } + + try { + $when = new DateTime('@'.$when); + $now = new DateTime('@'.$now); + } catch (Exception $ex) { + return null; + } + + $zone = $this->getTimeZone(); + + $when->setTimeZone($zone); + $now->setTimeZone($zone); + + if ($when->format('Y') !== $now->format('Y')) { + // Different year, so show "Feb 31 2075". + $format = 'M j Y'; + } else if ($when->format('Ymd') !== $now->format('Ymd')) { + // Same year but different month and day, so show "Feb 31". + $format = 'M j'; + } else { + // Same year, month and day so show a time of day. + $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; + $format = $this->getPreference($pref_time); + } + + return $when->format($format); + } + public function getPreference($key) { $preferences = $this->loadPreferences(); From a24f001b08effffee0848f0f1413b85f3d265a40 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 18 Mar 2016 06:22:37 -0700 Subject: [PATCH 21/25] Support pushing data into Git LFS Summary: Ref T7789. Ref T10604. This implements the `upload` action, which streams file data into Files. This makes Git LFS actually work, at least roughly. Test Plan: - Tracked files in an LFS repository. - Pushed LFS data (`git lfs track '*.png'; git add something.png; git commit -m ...; git push`). - Pulled LFS data (`git checkout master^; rm -rf .git/lfs; git checkout master; open something.png`). - Verified LFS refs show up in the gitlfsref table. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7789, T10604 Differential Revision: https://secure.phabricator.com/D15492 --- src/__phutil_library_map__.php | 2 + .../controller/DiffusionServeController.php | 103 +++++++++++++++++- .../PhabricatorFileUploadSource.php | 4 +- .../PhabricatorIteratorFileUploadSource.php | 25 +++++ 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 38f9505b24..34ec91844d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2501,6 +2501,7 @@ phutil_register_library_map(array( 'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php', 'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', + 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', @@ -6947,6 +6948,7 @@ phutil_register_library_map(array( 'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', + 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 9fca803008..31bcb2cbed 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -891,7 +891,12 @@ final class DiffusionServeController extends DiffusionController { } $path = $this->getGitLFSRequestPath($repository); - if ($path == 'objects/batch') { + $matches = null; + + if (preg_match('(^upload/(.*)\z)', $path, $matches)) { + $oid = $matches[1]; + return $this->serveGitLFSUploadRequest($repository, $viewer, $oid); + } else if ($path == 'objects/batch') { return $this->serveGitLFSBatchRequest($repository, $viewer); } else { return DiffusionGitLFSResponse::newErrorResponse( @@ -947,7 +952,7 @@ final class DiffusionServeController extends DiffusionController { if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) - ->withPHIDs(array($file_phids)) + ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); } else { @@ -960,6 +965,7 @@ final class DiffusionServeController extends DiffusionController { $oid = idx($object, 'oid'); $size = idx($object, 'size'); $ref = idx($refs, $oid); + $error = null; // NOTE: If we already have a ref for this object, we only emit a // "download" action. The client should not upload the file again. @@ -968,9 +974,26 @@ final class DiffusionServeController extends DiffusionController { if ($ref) { $file = idx($files, $ref->getFilePHID()); if ($file) { + // Git LFS may prompt users for authentication if the action does + // not provide an "Authorization" header and does not have a query + // parameter named "token". See here for discussion: + // + $no_authorization = 'Basic '.base64_encode('none'); + $get_uri = $file->getCDNURIWithToken(); $actions['download'] = array( 'href' => $get_uri, + 'header' => array( + 'Authorization' => $no_authorization, + ), + ); + } else { + $error = array( + 'code' => 404, + 'message' => pht( + 'Object "%s" was previously uploaded, but no longer exists '. + 'on this server.', + $oid), ); } } else if ($want_upload) { @@ -995,11 +1018,20 @@ final class DiffusionServeController extends DiffusionController { ); } - $output[] = array( + $object = array( 'oid' => $oid, 'size' => $size, - 'actions' => $actions, ); + + if ($actions) { + $object['actions'] = $actions; + } + + if ($error) { + $object['error'] = $error; + } + + $output[] = $object; } $output = array( @@ -1010,6 +1042,69 @@ final class DiffusionServeController extends DiffusionController { ->setContent($output); } + private function serveGitLFSUploadRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $oid) { + + $ref = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes(array($oid)) + ->executeOne(); + if ($ref) { + return DiffusionGitLFSResponse::newErrorResponse( + 405, + pht( + 'Content for object "%s" is already known to this server. It can '. + 'not be uploaded again.', + $oid)); + } + + $request_stream = new AphrontRequestStream(); + $request_iterator = $request_stream->getIterator(); + $hashing_iterator = id(new PhutilHashingIterator($request_iterator)) + ->setAlgorithm('sha256'); + + $source = id(new PhabricatorIteratorFileUploadSource()) + ->setName('lfs-'.$oid) + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) + ->setIterator($hashing_iterator); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file = $source->uploadFile(); + unset($unguarded); + + $hash = $hashing_iterator->getHash(); + if ($hash !== $oid) { + return DiffusionGitLFSResponse::newErrorResponse( + 400, + pht( + 'Uploaded data is corrupt or invalid. Expected hash "%s", actual '. + 'hash "%s".', + $oid, + $hash)); + } + + $ref = id(new PhabricatorRepositoryGitLFSRef()) + ->setRepositoryPHID($repository->getPHID()) + ->setObjectHash($hash) + ->setByteSize($file->getByteSize()) + ->setAuthorPHID($viewer->getPHID()) + ->setFilePHID($file->getPHID()); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + // Attach the file to the repository to give users permission + // to access it. + $file->attachToObject($repository->getPHID()); + $ref->save(); + unset($unguarded); + + // This is just a plain HTTP 200 with no content, which is what `git lfs` + // expects. + return new DiffusionGitLFSResponse(); + } + private function newGitLFSHTTPAuthorization( PhabricatorRepository $repository, PhabricatorUser $viewer, diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index 086fd3345e..1d38402066 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -72,7 +72,9 @@ abstract class PhabricatorFileUploadSource $data->rewind(); $this->didRewind = true; } else { - $data->next(); + if ($data->valid()) { + $data->next(); + } } if (!$data->valid()) { diff --git a/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php new file mode 100644 index 0000000000..b8d18936e6 --- /dev/null +++ b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php @@ -0,0 +1,25 @@ +iterator = $iterator; + return $this; + } + + public function getIterator() { + return $this->iterator; + } + + protected function newDataIterator() { + return $this->getIterator(); + } + + protected function getDataLength() { + return null; + } + +} From 78e36d6b17a534bc4130886b07908cd3909ae39d Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 18 Mar 2016 07:16:24 -0700 Subject: [PATCH 22/25] Implement DestructibleInterface on GitLFS refs Summary: Ref T7789. Make sure these get cleaned up when a repository is destroyed. Test Plan: - Created a new repository. - Pushed some LFS data to it. - Used `bin/remove destroy` to nuke it. - Verified the LFS stuff was cleaned up and the underlying files were destroyed (`SELECT * FROM repository_gitlfsref`, etc). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D15493 --- src/__phutil_library_map__.php | 1 + .../storage/PhabricatorRepository.php | 8 +++++++ .../PhabricatorRepositoryGitLFSRef.php | 23 ++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 34ec91844d..02a011ec72 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -7672,6 +7672,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryGitLFSRef' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'PhabricatorRepositoryGitLFSRefQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryGraphCache' => 'Phobject', diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index fbc8e38193..7068657acc 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2435,6 +2435,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $engine->destroyObject($atom); } + $lfs_refs = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($engine->getViewer()) + ->withRepositoryPHIDs(array($phid)) + ->execute(); + foreach ($lfs_refs as $ref) { + $engine->destroyObject($ref); + } + $this->saveTransaction(); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php index c049b4897a..507af99cb4 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php +++ b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php @@ -2,7 +2,9 @@ final class PhabricatorRepositoryGitLFSRef extends PhabricatorRepositoryDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorDestructibleInterface { protected $repositoryPHID; protected $objectHash; @@ -48,4 +50,23 @@ final class PhabricatorRepositoryGitLFSRef } +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $file_phid = $this->getFilePHID(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($engine->getViewer()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if ($file) { + $engine->destroyObject($file); + } + + $this->delete(); + } + } From 61ab7afc9c8822c16054c2336d2023655a3d43fa Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 18 Mar 2016 08:13:20 -0700 Subject: [PATCH 23/25] Make Diffusion do an alright job on Git LFS objects Summary: Ref T7789. This isn't the most perfect UI imaginable, but it's similar to what GitHub does and seems reasonable. Test Plan: {F1180271} {F1180272} Reviewers: chad Reviewed By: chad Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D15494 --- .../controller/DiffusionBrowseController.php | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 81adacf7c9..56d00d53ce 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -192,7 +192,25 @@ final class DiffusionBrowseController extends DiffusionController { } $data = $file->loadFileData(); - if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + + $ref = $this->getGitLFSRef($repository, $data); + if ($ref) { + if ($view == 'git-lfs') { + $file = $this->loadGitLFSFile($ref); + + // Rename the file locally so we generate a better vanity URI for + // it. In storage, it just has a name like "lfs-13f9a94c0923...", + // since we don't get any hints about possible human-readable names + // at upload time. + $basename = basename($drequest->getPath()); + $file->makeEphemeral(); + $file->setName($basename); + + return $file->getRedirectResponse(); + } else { + $corpus = $this->buildGitLFSCorpus($ref); + } + } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { @@ -930,7 +948,7 @@ final class DiffusionBrowseController extends DiffusionController { return $button; } - private function renderFileButton($file_uri = null) { + private function renderFileButton($file_uri = null, $label = null) { $base_uri = $this->getRequest()->getRequestURI(); @@ -944,6 +962,10 @@ final class DiffusionBrowseController extends DiffusionController { $icon = 'fa-file-text'; } + if ($label !== null) { + $text = $label; + } + $button = id(new PHUIButtonView()) ->setTag('a') ->setText($text) @@ -953,6 +975,21 @@ final class DiffusionBrowseController extends DiffusionController { return $button; } + private function renderGitLFSButton() { + $viewer = $this->getViewer(); + + $uri = $this->getRequest()->getRequestURI(); + $href = $uri->alter('view', 'git-lfs'); + + $text = pht('Download from Git LFS'); + $icon = 'fa-download'; + + return id(new PHUIButtonView()) + ->setTag('a') + ->setText($text) + ->setHref($href) + ->setIcon($icon); + } private function buildDisplayRows( array $lines, @@ -1889,4 +1926,90 @@ final class DiffusionBrowseController extends DiffusionController { $corpus); } + private function getGitLFSRef(PhabricatorRepository $repository, $data) { + if (!$repository->canUseGitLFS()) { + return null; + } + + $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])'; + if (!preg_match($lfs_pattern, $data)) { + return null; + } + + $matches = null; + if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) { + return null; + } + + $hash = $matches[1]; + $hash = trim($hash); + + return id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($this->getViewer()) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes(array($hash)) + ->executeOne(); + } + + private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) { + // TODO: We should probably test if we can load the file PHID here and + // show the user an error if we can't, rather than making them click + // through to hit an error. + + $header = id(new PHUIHeaderView()) + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->setHeaderIcon('fa-archive'); + + $severity = PHUIInfoView::SEVERITY_NOTICE; + + $messages = array(); + $messages[] = pht( + 'This %s file is stored in Git Large File Storage.', + phutil_format_bytes($ref->getByteSize())); + + try { + $file = $this->loadGitLFSFile($ref); + $data = $this->renderGitLFSButton(); + $header->addActionLink($data); + } catch (Exception $ex) { + $severity = PHUIInfoView::SEVERITY_ERROR; + $messages[] = pht('The data for this file could not be loaded.'); + } + + $raw = $this->renderFileButton(null, pht('View Raw LFS Pointer')); + $header->addActionLink($raw); + + $corpus = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setCollapsed(true); + + if ($messages) { + $corpus->setInfoView( + id(new PHUIInfoView()) + ->setSeverity($severity) + ->setErrors($messages)); + } + + return $corpus; + } + + private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) { + $viewer = $this->getViewer(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($ref->getFilePHID())) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'Failed to load file object for Git LFS ref "%s"!', + $ref->getObjectHash())); + } + + return $file; + } + + } From 01885cad1cbdea61142f770b2182d55c98482170 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 18 Mar 2016 12:01:15 -0700 Subject: [PATCH 24/25] Couple of Diffusion tweaks Summary: - Fix spacing on InfoView inside collasped boxes - Fix spacing on stacked PropertyLists in TwoColumn - Fix spacing on Readmes on Tablets - Fix unset variable on importing commits Test Plan: Review each of the above cases. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15496 --- resources/celerity/map.php | 8 ++++---- .../diffusion/controller/DiffusionCommitController.php | 2 ++ .../rsrc/css/application/diffusion/diffusion-readme.css | 1 + webroot/rsrc/css/phui/phui-two-column-view.css | 9 +++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3209d1c6ed..48a8367edb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -65,7 +65,7 @@ return array( 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', 'rsrc/css/application/diffusion/diffusion-icons.css' => '3311444d', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '356a4f3c', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', 'rsrc/css/application/diffusion/diffusion-source.css' => '68b30fd3', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'a0173eba', - 'rsrc/css/phui/phui-two-column-view.css' => 'c110d0c3', + 'rsrc/css/phui/phui-two-column-view.css' => '37d704f3', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -554,7 +554,7 @@ return array( 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', 'diffusion-icons-css' => '3311444d', - 'diffusion-readme-css' => '356a4f3c', + 'diffusion-readme-css' => '297373eb', 'diffusion-source-css' => '68b30fd3', 'diviner-shared-css' => 'aa3656aa', 'font-aleo' => '8bdb2835', @@ -846,7 +846,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'a0173eba', - 'phui-two-column-view-css' => 'c110d0c3', + 'phui-two-column-view-css' => '37d704f3', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index b14477ffa3..c6ac32bf7f 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -635,6 +635,8 @@ final class DiffusionCommitController extends DiffusionController { $author = $author_name; $image_uri = null; $image_href = null; + } else { + return null; } $author = phutil_tag('strong', array(), $author); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-readme.css b/webroot/rsrc/css/application/diffusion/diffusion-readme.css index f7f8506e2d..bb661bb2c8 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-readme.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-readme.css @@ -2,6 +2,7 @@ * @provides diffusion-readme-css */ +.device .diffusion-readme-view .phui-document-fluid .phui-document-view, .device-desktop .diffusion-readme-view .phui-document-fluid .phui-document-view { margin: 0; diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 1496a3b0f2..7997b196fe 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -131,6 +131,11 @@ padding: 16px 0; } +.device-desktop .phui-two-column-view + .phui-property-list-properties-wrap.phui-property-list-stacked { + padding: 0 16px; +} + .device .phui-two-column-view .phui-property-list-container { padding: 12px 8px; } @@ -192,7 +197,3 @@ .phui-info-view { margin: 0; } - -.phui-two-column-view .phui-object-box .phui-header-shell + .phui-info-view { - margin: 16px 16px 0 16px; -} From 981f3a9068bbd8fe9a868e2de00dccf0470b8ad9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 18 Mar 2016 15:56:12 -0700 Subject: [PATCH 25/25] When marking up Phurl URLs for mail, use absolute URLs Summary: Fixes T10625. Test Plan: Faked this locallly and it looked OK, I'll check the mail in production. :3333 Reviewers: chad Reviewed By: chad Maniphest Tasks: T10625 Differential Revision: https://secure.phabricator.com/D15497 --- .../PhabricatorPhurlLinkRemarkupRule.php | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php b/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php index aca7c14dc7..4ef59b300c 100644 --- a/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php +++ b/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php @@ -18,7 +18,9 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule { public function markupLink(array $matches) { $engine = $this->getEngine(); $viewer = $engine->getConfig('viewer'); + $text_mode = $engine->isTextMode(); + $html_mode = $engine->isHTMLMailMode(); if (!$this->isFlatText($matches[0])) { return $matches[0]; @@ -28,46 +30,45 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule { $monogram = null; $is_monogram = '/^U(?P[1-9]\d*)/'; + $query = id(new PhabricatorPhurlURLQuery()) + ->setViewer($viewer); + if (preg_match($is_monogram, $ref, $monogram)) { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($monogram[1])) - ->execute(); + $query->withIDs(array($monogram[1])); } else if (ctype_digit($ref)) { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($ref)) - ->execute(); + $query->withIDs(array($ref)); } else { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withAliases(array($ref)) - ->execute(); + $query->withAliases(array($ref)); } - $phurl = head($phurls); + $phurl = $query->executeOne(); + if (!$phurl) { + return $matches[0]; + } - if ($phurl) { - if ($text_mode) { - return $phurl->getDisplayName(). - ' <'. - $phurl->getRedirectURI(). - '>'; - } + $uri = $phurl->getRedirectURI(); + $name = $phurl->getDisplayName(); + if ($text_mode || $html_mode) { + $uri = PhabricatorEnv::getProductionURI($uri); + } + + if ($text_mode) { + return pht( + '%s <%s>', + $name, + $uri); + } else { $link = phutil_tag( 'a', array( - 'href' => $phurl->getRedirectURI(), + 'href' => $uri, 'target' => '_blank', ), - $phurl->getDisplayName()); - - return $this->getEngine()->storeText($link); - } else { - return $matches[0]; + $name); } + + return $this->getEngine()->storeText($link); } - }