diff --git a/src/aphront/AphrontController.php b/src/aphront/AphrontController.php index 6d84c46195..a591271270 100644 --- a/src/aphront/AphrontController.php +++ b/src/aphront/AphrontController.php @@ -1,67 +1,86 @@ delegatingController = $delegating_controller; return $this; } public function getDelegatingController() { return $this->delegatingController; } public function willBeginExecution() { return; } public function willProcessRequest(array $uri_data) { return; } public function didProcessRequest($response) { return $response; } abstract public function processRequest(); final public function __construct(AphrontRequest $request) { $this->request = $request; } final public function getRequest() { return $this->request; } final public function delegateToController(AphrontController $controller) { $controller->setDelegatingController($this); $application = $this->getCurrentApplication(); if ($application) { $controller->setCurrentApplication($application); } return $controller->processRequest(); } final public function setCurrentApplication( PhabricatorApplication $current_application) { $this->currentApplication = $current_application; return $this; } final public function getCurrentApplication() { return $this->currentApplication; } + public function getDefaultResourceSource() { + throw new Exception( + pht( + 'A Controller must implement getDefaultResourceSource() before you '. + 'can invoke requireResource() or initBehavior().')); + } + + public function requireResource($symbol) { + $response = CelerityAPI::getStaticResourceResponse(); + $response->requireResource($symbol, $this->getDefaultResourceSource()); + return $this; + } + + public function initBehavior($name, $config = array()) { + Javelin::initBehavior( + $name, + $config, + $this->getDefaultResourceSource()); + } + } diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 86009fd9fc..4d7d6e3243 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -1,411 +1,416 @@ getRequest(); if ($request->getUser()) { // NOTE: Unit tests can set a user explicitly. Normal requests are not // permitted to do this. PhabricatorTestCase::assertExecutingUnitTests(); $user = $request->getUser(); } else { $user = new PhabricatorUser(); $phusr = $request->getCookie('phusr'); $phsid = $request->getCookie('phsid'); if (strlen($phusr) && $phsid) { $info = queryfx_one( $user->establishConnection('r'), 'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID AND s.type LIKE %> AND s.sessionKey = %s', $user->getTableName(), 'phabricator_session', 'web-', PhabricatorHash::digest($phsid)); if ($info) { $user->loadFromArray($info); } } $request->setUser($user); } $translation = $user->getTranslation(); if ($translation && $translation != PhabricatorEnv::getEnvConfig('translation.provider')) { $translation = newv($translation, array()); PhutilTranslator::getInstance() ->setLanguage($translation->getLanguage()) ->addTranslations($translation->getTranslations()); } $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } if ($this->shouldRequireEnabledUser()) { if ($user->isLoggedIn() && !$user->getIsApproved()) { $controller = new PhabricatorAuthNeedsApprovalController($request); return $this->delegateToController($controller); } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController($request); return $this->delegateToController($controller); } } $event = new PhabricatorEvent( PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST, array( 'request' => $request, 'controller' => $this, )); $event->setUser($user); PhutilEventEngine::dispatchEvent($event); $checker_controller = $event->getValue('controller'); if ($checker_controller != $this) { return $this->delegateToController($checker_controller); } if ($this->shouldRequireLogin()) { // This actually means we need either: // - a valid user, or a public controller; and // - permission to see the application. $auth_class = 'PhabricatorApplicationAuth'; $auth_application = PhabricatorApplication::getByClass($auth_class); $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { $login_controller = new PhabricatorAuthStartController($request); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { if (!$user->getIsEmailVerified()) { $controller = new PhabricatorMustVerifyEmailController($request); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } } } // If the user doesn't have access to the application, don't let them use // any of its controllers. We query the application in order to generate // a policy exception if the viewer doesn't have permission. $application = $this->getCurrentApplication(); if ($application) { id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withPHIDs(array($application->getPHID())) ->executeOne(); } } // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } } public function buildStandardPageView() { $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->setController($this); return $view; } public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); $page->appendChild($view); $response = new AphrontWebpageResponse(); $response->setContent($page->render()); return $response; } public function getApplicationURI($path = '') { if (!$this->getCurrentApplication()) { throw new Exception("No application!"); } return $this->getCurrentApplication()->getApplicationURI($path); } public function buildApplicationPage($view, array $options) { $page = $this->buildStandardPageView(); $title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ? 'Phabricator' : pht('Bacon Ice Cream for Breakfast'); $application = $this->getCurrentApplication(); $page->setTitle(idx($options, 'title', $title)); if ($application) { $page->setApplicationName($application->getName()); if ($application->getTitleGlyph()) { $page->setGlyph($application->getTitleGlyph()); } } if (!($view instanceof AphrontSideNavFilterView)) { $nav = new AphrontSideNavFilterView(); $nav->appendChild($view); $view = $nav; } $user = $this->getRequest()->getUser(); $view->setUser($user); $page->appendChild($view); $object_phids = idx($options, 'pageObjects', array()); if ($object_phids) { $page->appendPageObjects($object_phids); foreach ($object_phids as $object_phid) { PhabricatorFeedStoryNotification::updateObjectNotificationViews( $user, $object_phid); } } if (idx($options, 'device')) { $page->setDeviceReady(true); } $page->setShowChrome(idx($options, 'chrome', true)); $application_menu = $this->buildApplicationMenu(); if ($application_menu) { $page->setApplicationMenu($application_menu); } $response = new AphrontWebpageResponse(); return $response->setContent($page->render()); } public function didProcessRequest($response) { $request = $this->getRequest(); $response->setRequest($request); $seen = array(); while ($response instanceof AphrontProxyResponse) { $hash = spl_object_hash($response); if (isset($seen[$hash])) { $seen[] = get_class($response); throw new Exception( "Cycle while reducing proxy responses: ". implode(' -> ', $seen)); } $seen[$hash] = get_class($response); $response = $response->reduceProxyResponse(); } if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->setController($this); $view->appendChild(phutil_tag( 'div', array('style' => 'padding: 2em 0;'), $response->buildResponseString())); $page_response = new AphrontWebpageResponse(); $page_response->setContent($view->render()); $page_response->setHTTPResponseCode($response->getHTTPResponseCode()); return $page_response; } else { $response->getDialog()->setIsStandalone(true); return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } return $response; } protected function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( "Attempting to access handle which wasn't loaded: {$phid}"); } return $this->handles[$phid]; } protected function loadHandles(array $phids) { $phids = array_filter($phids); $this->handles = $this->loadViewerHandles($phids); return $this; } protected function getLoadedHandles() { return $this->handles; } protected function loadViewerHandles(array $phids) { return id(new PhabricatorHandleQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs($phids) ->execute(); } /** * Render a list of links to handles, identified by PHIDs. The handles must * already be loaded. * * @param list List of PHIDs to render links to. * @param string Style, one of "\n" (to put each item on its own line) * or "," (to list items inline, separated by commas). * @return string Rendered list of handle links. */ protected function renderHandlesForPHIDs(array $phids, $style = "\n") { $style_map = array( "\n" => phutil_tag('br'), ',' => ', ', ); if (empty($style_map[$style])) { throw new Exception("Unknown handle list style '{$style}'!"); } return implode_selected_handle_links($style_map[$style], $this->getLoadedHandles(), array_filter($phids)); } protected function buildApplicationMenu() { return null; } protected function buildApplicationCrumbs() { $crumbs = array(); $application = $this->getCurrentApplication(); if ($application) { $sprite = $application->getIconName(); if (!$sprite) { $sprite = 'application'; } $crumbs[] = id(new PhabricatorCrumbView()) ->setHref($this->getApplicationURI()) ->setIcon($sprite); } $view = new PhabricatorCrumbsView(); foreach ($crumbs as $crumb) { $view->addCrumb($crumb); } return $view; } protected function hasApplicationCapability($capability) { return PhabricatorPolicyFilter::hasCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function requireApplicationCapability($capability) { PhabricatorPolicyFilter::requireCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function explainApplicationCapability( $capability, $positive_message, $negative_message) { $can_act = $this->hasApplicationCapability($capability); if ($can_act) { $message = $positive_message; $icon_name = 'enable-grey'; } else { $message = $negative_message; $icon_name = 'lock'; } $icon = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon($icon_name); require_celerity_resource('policy-css'); $phid = $this->getCurrentApplication()->getPHID(); $explain_uri = "/policy/explain/{$phid}/{$capability}/"; $message = phutil_tag( 'div', array( 'class' => 'policy-capability-explanation', ), array( $icon, javelin_tag( 'a', array( 'href' => $explain_uri, 'sigil' => 'workflow', ), $message), )); return array($can_act, $message); } + public function getDefaultResourceSource() { + return 'phabricator'; + } + + } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 14d37ffb29..8e15dd7de3 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1,928 +1,928 @@ revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $viewer_is_anonymous = !$user->isLoggedIn(); $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($this->revisionID)) ->setViewer($request->getUser()) ->needRelationships(true) ->needReviewerStatus(true) ->needReviewerAuthority(true) ->executeOne(); if (!$revision) { return new Aphront404Response(); } $diffs = id(new DifferentialDiffQuery()) ->setViewer($request->getUser()) ->withRevisionIDs(array($this->revisionID)) ->execute(); $diffs = array_reverse($diffs, $preserve_keys = true); if (!$diffs) { throw new Exception( "This revision has no diffs. Something has gone quite wrong."); } $diff_vs = $request->getInt('vs'); $target_id = $request->getInt('id'); $target = idx($diffs, $target_id, end($diffs)); $target_manual = $target; if (!$target_id) { foreach ($diffs as $diff) { if ($diff->getCreationMethod() != 'commit') { $target_manual = $diff; } } } if (empty($diffs[$diff_vs])) { $diff_vs = null; } $arc_project = $target->loadArcanistProject(); $repository = ($arc_project ? $arc_project->loadRepository() : null); list($changesets, $vs_map, $vs_changesets, $rendering_references) = $this->loadChangesetsAndVsMap( $target, idx($diffs, $diff_vs), $repository); if ($request->getExists('download')) { return $this->buildRawDiffResponse( $revision, $changesets, $vs_changesets, $vs_map, $repository); } $props = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $target_manual->getID()); $props = mpull($props, 'getData', 'getName'); $aux_fields = $this->loadAuxiliaryFields($revision); $comments = $revision->loadComments(); $all_changesets = $changesets; $inlines = $this->loadInlineComments( $revision, $all_changesets); $object_phids = array_merge( $revision->getReviewers(), $revision->getCCPHIDs(), $revision->loadCommitPHIDs(), array( $revision->getAuthorPHID(), $user->getPHID(), ), mpull($comments, 'getAuthorPHID')); foreach ($comments as $comment) { foreach ($comment->getRequiredHandlePHIDs() as $phid) { $object_phids[] = $phid; } } foreach ($revision->getAttached() as $type => $phids) { foreach ($phids as $phid => $info) { $object_phids[] = $phid; } } $aux_phids = array(); foreach ($aux_fields as $key => $aux_field) { $aux_field->setDiff($target); $aux_field->setManualDiff($target_manual); $aux_field->setDiffProperties($props); $aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView(); } $object_phids = array_merge($object_phids, array_mergev($aux_phids)); $object_phids = array_unique($object_phids); $handles = $this->loadViewerHandles($object_phids); foreach ($aux_fields as $key => $aux_field) { // Make sure each field only has access to handles it specifically // requested, not all handles. Otherwise you can get a field which works // only in the presence of other fields. $aux_field->setHandles(array_select_keys($handles, $aux_phids[$key])); } $reviewer_warning = null; if ($revision->getStatus() == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { $has_live_reviewer = false; foreach ($revision->getReviewers() as $reviewer) { if (!$handles[$reviewer]->isDisabled()) { $has_live_reviewer = true; break; } } if (!$has_live_reviewer) { $reviewer_warning = new AphrontErrorView(); $reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $reviewer_warning->setTitle(pht('No Active Reviewers')); if ($revision->getReviewers()) { $reviewer_warning->appendChild( phutil_tag( 'p', array(), pht('All specified reviewers are disabled and this revision '. 'needs review. You may want to add some new reviewers.'))); } else { $reviewer_warning->appendChild( phutil_tag( 'p', array(), pht('This revision has no specified reviewers and needs '. 'review. You may want to add some reviewers.'))); } } } $request_uri = $request->getRequestURI(); $limit = 100; $large = $request->getStr('large'); if (count($changesets) > $limit && !$large) { $count = count($changesets); $warning = new AphrontErrorView(); $warning->setTitle('Very Large Diff'); $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $warning->appendChild(hsprintf( '%s %s', pht( 'This diff is very large and affects %s files. Load each file '. 'individually.', new PhutilNumber($count)), phutil_tag( 'a', array( 'href' => $request_uri ->alter('large', 'true') ->setFragment('toc'), ), pht('Show All Files Inline')))); $warning = $warning->render(); $my_inlines = id(new DifferentialInlineCommentQuery()) ->withDraftComments($user->getPHID(), $this->revisionID) ->execute(); $visible_changesets = array(); foreach ($inlines + $my_inlines as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { $visible_changesets[$changeset_id] = $changesets[$changeset_id]; } } if (!empty($props['arc:lint'])) { $changeset_paths = mpull($changesets, null, 'getFilename'); foreach ($props['arc:lint'] as $lint) { $changeset = idx($changeset_paths, $lint['path']); if ($changeset) { $visible_changesets[$changeset->getID()] = $changeset; } } } } else { $warning = null; $visible_changesets = $changesets; } $revision_detail = id(new DifferentialRevisionDetailView()) ->setUser($user) ->setRevision($revision) ->setDiff(end($diffs)) ->setAuxiliaryFields($aux_fields) ->setURI($request->getRequestURI()); $actions = $this->getRevisionActions($revision); $custom_renderer_class = PhabricatorEnv::getEnvConfig( 'differential.revision-custom-detail-renderer'); if ($custom_renderer_class) { // TODO: build a better version of the action links and deprecate the // whole DifferentialRevisionDetailRenderer class. $custom_renderer = newv($custom_renderer_class, array()); $custom_renderer->setUser($user); $custom_renderer->setDiff($target); if ($diff_vs) { $custom_renderer->setVSDiff($diffs[$diff_vs]); } $actions = array_merge( $actions, $custom_renderer->generateActionLinks($revision, $target_manual)); } $whitespace = $request->getStr( 'whitespace', DifferentialChangesetParser::WHITESPACE_IGNORE_ALL); if ($arc_project) { list($symbol_indexes, $project_phids) = $this->buildSymbolIndexes( $arc_project, $visible_changesets); } else { $symbol_indexes = array(); $project_phids = null; } $revision_detail->setActions($actions); $revision_detail->setUser($user); $comment_view = new DifferentialRevisionCommentListView(); $comment_view->setComments($comments); $comment_view->setHandles($handles); $comment_view->setInlineComments($inlines); $comment_view->setChangesets($all_changesets); $comment_view->setUser($user); $comment_view->setTargetDiff($target); $comment_view->setVersusDiffID($diff_vs); if ($arc_project) { Javelin::initBehavior( 'repository-crossreference', array( 'section' => $comment_view->getID(), 'projects' => $project_phids, )); } $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets($changesets); $changeset_view->setVisibleChangesets($visible_changesets); 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($user); $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('Diff '.$target->getID()); $diff_history = new DifferentialRevisionUpdateHistoryView(); $diff_history->setDiffs($diffs); $diff_history->setSelectedVersusDiffID($diff_vs); $diff_history->setSelectedDiffID($target->getID()); $diff_history->setSelectedWhitespace($whitespace); $diff_history->setUser($user); $local_view = new DifferentialLocalCommitsView(); $local_view->setUser($user); $local_view->setLocalCommits(idx($props, 'local:commits')); if ($repository) { $other_revisions = $this->loadOtherRevisions( $changesets, $target, $repository); } else { $other_revisions = array(); } $other_view = null; if ($other_revisions) { $other_view = $this->renderOtherRevisions($other_revisions); } $toc_view = new DifferentialDiffTableOfContentsView(); $toc_view->setChangesets($changesets); $toc_view->setVisibleChangesets($visible_changesets); $toc_view->setRenderingReferences($rendering_references); $toc_view->setUnitTestData(idx($props, 'arc:unit', array())); if ($repository) { $toc_view->setRepository($repository); } $toc_view->setDiff($target); $toc_view->setUser($user); $toc_view->setRevisionID($revision->getID()); $toc_view->setWhitespace($whitespace); $comment_form = null; if (!$viewer_is_anonymous) { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->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); $comment_form->setAuxFields($aux_fields); $comment_form->setActions($this->getRevisionCommentActions($revision)); $comment_form->setActionURI('/differential/comment/save/'); $comment_form->setUser($user); $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); } $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(array( $comment_view, $diff_history, $warning, $local_view, $toc_view, $other_view, $changeset_view, )); if ($comment_form) { $page_pane->appendChild($comment_form); } else { // TODO: For now, just use this to get "Login to Comment". $page_pane->appendChild( id(new PhabricatorApplicationTransactionCommentView()) ->setUser($user) ->setRequestURI($request->getRequestURI())); } $object_id = 'D'.$revision->getID(); $top_anchor = id(new PhabricatorAnchorView()) ->setAnchorName('top') ->setNavigationMarker(true); $content = array( $reviewer_warning, $top_anchor, $revision_detail, $page_pane, ); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($object_id, '/'.$object_id); $prefs = $user->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; if ($prefs->getPreference($pref_filetree)) { $collapsed = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED, false); $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setAnchorName('top') ->setTitle('D'.$revision->getID()) ->setBaseURI(new PhutilURI('/D'.$revision->getID())) ->setCollapsed((bool)$collapsed) ->build($changesets); $nav->appendChild($content); $nav->setCrumbs($crumbs); $content = $nav; } else { array_unshift($content, $crumbs); } return $this->buildApplicationPage( $content, array( 'title' => $object_id.' '.$revision->getTitle(), 'pageObjects' => array($revision->getPHID()), )); } private function getRevisionActions(DifferentialRevision $revision) { $user = $this->getRequest()->getUser(); $viewer_phid = $user->getPHID(); $viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs()); $logged_in = $this->getRequest()->getUser()->isLoggedIn(); $status = $revision->getStatus(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); $links = array(); $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $revision, PhabricatorPolicyCapability::CAN_EDIT); $links[] = array( 'icon' => 'edit', 'href' => "/differential/revision/edit/{$revision_id}/", 'name' => pht('Edit Revision'), 'disabled' => !$can_edit, 'sigil' => $can_edit ? null : 'workflow', ); if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $links[] = array( 'icon' => $viewer_is_cc ? 'disable' : 'check', 'href' => "/differential/subscribe/{$action}/{$revision_id}/", 'name' => $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'), 'instant' => $logged_in, 'disabled' => !$logged_in, 'sigil' => $can_edit ? null : 'workflow', ); } else { $links[] = array( 'icon' => 'enable', 'name' => pht('Automatically Subscribed'), 'disabled' => true, ); } - require_celerity_resource('phabricator-object-selector-css'); - require_celerity_resource('javelin-behavior-phabricator-object-selector'); + $this->requireResource('phabricator-object-selector-css'); + $this->requireResource('javelin-behavior-phabricator-object-selector'); $links[] = array( 'icon' => 'link', 'name' => pht('Edit Dependencies'), 'href' => "/search/attach/{$revision_phid}/DREV/dependencies/", 'sigil' => 'workflow', 'disabled' => !$can_edit, ); $maniphest = 'PhabricatorApplicationManiphest'; if (PhabricatorApplication::isClassInstalled($maniphest)) { $links[] = array( 'icon' => 'attach', 'name' => pht('Edit Maniphest Tasks'), 'href' => "/search/attach/{$revision_phid}/TASK/", 'sigil' => 'workflow', 'disabled' => !$can_edit, ); } $request_uri = $this->getRequest()->getRequestURI(); $links[] = array( 'icon' => 'download', 'name' => pht('Download Raw Diff'), 'href' => $request_uri->alter('download', 'true') ); return $links; } private function getRevisionCommentActions(DifferentialRevision $revision) { $actions = array( DifferentialAction::ACTION_COMMENT => true, ); $viewer = $this->getRequest()->getUser(); $viewer_phid = $viewer->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $status = $revision->getStatus(); $viewer_has_accepted = false; $viewer_has_rejected = false; $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED; $status_rejected = DifferentialReviewerStatus::STATUS_REJECTED; foreach ($revision->getReviewerStatus() as $reviewer) { if ($reviewer->getReviewerPHID() == $viewer_phid) { if ($reviewer->getStatus() == $status_accepted) { $viewer_has_accepted = true; } if ($reviewer->getStatus() == $status_rejected) { $viewer_has_rejected = true; } break; } } $allow_self_accept = PhabricatorEnv::getEnvConfig( 'differential.allow-self-accept'); $always_allow_close = PhabricatorEnv::getEnvConfig( 'differential.always-allow-close'); $allow_reopen = PhabricatorEnv::getEnvConfig( 'differential.allow-reopen'); if ($viewer_is_owner) { switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept; $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept; $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; $actions[DifferentialAction::ACTION_CLOSE] = true; break; case ArcanistDifferentialRevisionStatus::CLOSED: break; case ArcanistDifferentialRevisionStatus::ABANDONED: $actions[DifferentialAction::ACTION_RECLAIM] = true; break; } } else { switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::CLOSED: case ArcanistDifferentialRevisionStatus::ABANDONED: break; } if ($status != ArcanistDifferentialRevisionStatus::CLOSED) { $actions[DifferentialAction::ACTION_CLAIM] = true; $actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close; } } $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true; $actions[DifferentialAction::ACTION_ADDCCS] = true; $actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen && ($status == ArcanistDifferentialRevisionStatus::CLOSED); $actions = array_keys(array_filter($actions)); $actions_dict = array(); foreach ($actions as $action) { $actions_dict[$action] = DifferentialAction::getActionVerb($action); } return $actions_dict; } private function loadInlineComments( DifferentialRevision $revision, array &$changesets) { assert_instances_of($changesets, 'DifferentialChangeset'); $inline_comments = array(); $inline_comments = id(new DifferentialInlineCommentQuery()) ->withRevisionIDs(array($revision->getID())) ->withNotDraft(true) ->execute(); $load_changesets = array(); foreach ($inline_comments as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { continue; } $load_changesets[$changeset_id] = true; } $more_changesets = array(); if ($load_changesets) { $changeset_ids = array_keys($load_changesets); $more_changesets += id(new DifferentialChangeset()) ->loadAllWhere( 'id IN (%Ld)', $changeset_ids); } if ($more_changesets) { $changesets += $more_changesets; $changesets = msort($changesets, 'getSortKey'); } return $inline_comments; } private function loadChangesetsAndVsMap( DifferentialDiff $target, DifferentialDiff $diff_vs = null, PhabricatorRepository $repository = null) { $load_ids = array(); if ($diff_vs) { $load_ids[] = $diff_vs->getID(); } $load_ids[] = $target->getID(); $raw_changesets = id(new DifferentialChangeset()) ->loadAllWhere( 'diffID IN (%Ld)', $load_ids); $changeset_groups = mgroup($raw_changesets, 'getDiffID'); $changesets = idx($changeset_groups, $target->getID(), array()); $changesets = mpull($changesets, null, 'getID'); $refs = array(); $vs_map = array(); $vs_changesets = array(); if ($diff_vs) { $vs_id = $diff_vs->getID(); $vs_changesets_path_map = array(); foreach (idx($changeset_groups, $vs_id, array()) as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs); $vs_changesets_path_map[$path] = $changeset; $vs_changesets[$changeset->getID()] = $changeset; } foreach ($changesets as $key => $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $target); if (isset($vs_changesets_path_map[$path])) { $vs_map[$changeset->getID()] = $vs_changesets_path_map[$path]->getID(); $refs[$changeset->getID()] = $changeset->getID().'/'.$vs_changesets_path_map[$path]->getID(); unset($vs_changesets_path_map[$path]); } else { $refs[$changeset->getID()] = $changeset->getID(); } } foreach ($vs_changesets_path_map as $path => $changeset) { $changesets[$changeset->getID()] = $changeset; $vs_map[$changeset->getID()] = -1; $refs[$changeset->getID()] = $changeset->getID().'/-1'; } } else { foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } } $changesets = msort($changesets, 'getSortKey'); return array($changesets, $vs_map, $vs_changesets, $refs); } private function loadAuxiliaryFields(DifferentialRevision $revision) { $aux_fields = DifferentialFieldSelector::newSelector() ->getFieldSpecifications(); foreach ($aux_fields as $key => $aux_field) { if (!$aux_field->shouldAppearOnRevisionView()) { unset($aux_fields[$key]); } else { $aux_field->setUser($this->getRequest()->getUser()); } } $aux_fields = DifferentialAuxiliaryField::loadFromStorage( $revision, $aux_fields); return $aux_fields; } private function buildSymbolIndexes( PhabricatorRepositoryArcanistProject $arc_project, array $visible_changesets) { assert_instances_of($visible_changesets, 'DifferentialChangeset'); $engine = PhabricatorSyntaxHighlighter::newEngine(); $langs = $arc_project->getSymbolIndexLanguages(); if (!$langs) { return array(array(), array()); } $symbol_indexes = array(); $project_phids = array_merge( array($arc_project->getPHID()), nonempty($arc_project->getSymbolIndexProjects(), array())); $indexed_langs = array_fill_keys($langs, true); foreach ($visible_changesets as $key => $changeset) { $lang = $engine->getLanguageFromFilename($changeset->getFilename()); if (isset($indexed_langs[$lang])) { $symbol_indexes[$key] = array( 'lang' => $lang, 'projects' => $project_phids, ); } } return array($symbol_indexes, $project_phids); } private function loadOtherRevisions( array $changesets, DifferentialDiff $target, PhabricatorRepository $repository) { assert_instances_of($changesets, 'DifferentialChangeset'); $paths = array(); foreach ($changesets as $changeset) { $paths[] = $changeset->getAbsoluteRepositoryPath( $repository, $target); } if (!$paths) { return array(); } $path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs(); if (!$path_map) { return array(); } $query = id(new DifferentialRevisionQuery()) ->setViewer($this->getRequest()->getUser()) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED) ->setLimit(10) ->needRelationships(true); foreach ($path_map as $path => $path_id) { $query->withPath($repository->getID(), $path_id); } $results = $query->execute(); // Strip out *this* revision. foreach ($results as $key => $result) { if ($result->getID() == $this->revisionID) { unset($results[$key]); } } return $results; } private function renderOtherRevisions(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $user = $this->getRequest()->getUser(); $view = id(new DifferentialRevisionListView()) ->setRevisions($revisions) ->setFields(DifferentialRevisionListView::getDefaultFields($user)) ->setUser($user) ->loadAssets(); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Open Revisions Affecting These Files')) ->appendChild($view); } /** * Note this code is somewhat similar to the buildPatch method in * @{class:DifferentialReviewRequestMail}. * * @return @{class:AphrontRedirectResponse} */ private function buildRawDiffResponse( DifferentialRevision $revision, array $changesets, array $vs_changesets, array $vs_map, PhabricatorRepository $repository = null) { assert_instances_of($changesets, 'DifferentialChangeset'); assert_instances_of($vs_changesets, 'DifferentialChangeset'); $viewer = $this->getRequest()->getUser(); foreach ($changesets as $changeset) { $changeset->attachHunks($changeset->loadHunks()); } $diff = new DifferentialDiff(); $diff->attachChangesets($changesets); $raw_changes = $diff->buildChangesList(); $changes = array(); foreach ($raw_changes as $changedict) { $changes[] = ArcanistDiffChange::newFromDictionary($changedict); } $loader = id(new PhabricatorFileBundleLoader()) ->setViewer($viewer); $bundle = ArcanistBundle::newFromChanges($changes); $bundle->setLoadFileDataCallback(array($loader, 'loadFileData')); $vcs = $repository ? $repository->getVersionControlSystem() : null; switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $raw_diff = $bundle->toGitPatch(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: default: $raw_diff = $bundle->toUnifiedDiff(); break; } $request_uri = $this->getRequest()->getRequestURI(); // this ends up being something like // D123.diff // or the verbose // D123.vs123.id123.whitespaceignore-all.diff // lame but nice to include these options $file_name = ltrim($request_uri->getPath(), '/').'.'; foreach ($request_uri->getQueryParams() as $key => $value) { if ($key == 'download') { continue; } $file_name .= $key.$value.'.'; } $file_name .= 'diff'; $file = PhabricatorFile::buildFromFileDataOrHash( $raw_diff, array( 'name' => $file_name, 'ttl' => (60 * 60 * 24), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, )); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file->attachToObject( $this->getRequest()->getUser(), $revision->getPHID()); unset($unguarded); return id(new AphrontRedirectResponse())->setURI($file->getBestURI()); } } diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php index 822a85d6ac..a95ee9254f 100644 --- a/src/applications/differential/view/DifferentialAddCommentView.php +++ b/src/applications/differential/view/DifferentialAddCommentView.php @@ -1,205 +1,205 @@ revision = $revision; return $this; } public function setAuxFields(array $aux_fields) { assert_instances_of($aux_fields, 'DifferentialFieldSpecification'); $this->auxFields = $aux_fields; return $this; } public function setActions(array $actions) { $this->actions = $actions; return $this; } public function setActionURI($uri) { $this->actionURI = $uri; return $this; } public function setDraft(PhabricatorDraft $draft = null) { $this->draft = $draft; return $this; } public function setReviewers(array $names) { $this->reviewers = $names; return $this; } public function setCCs(array $names) { $this->ccs = $names; return $this; } public function render() { - require_celerity_resource('differential-revision-add-comment-css'); + $this->requireResource('differential-revision-add-comment-css'); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $revision = $this->revision; $action = null; if ($this->draft) { $action = idx($this->draft->getMetadata(), 'action'); } $enable_reviewers = DifferentialAction::allowReviewers($action); $enable_ccs = ($action == DifferentialAction::ACTION_ADDCCS); $add_reviewers_labels = array( 'add_reviewers' => pht('Add Reviewers'), 'request_review' => pht('Add Reviewers'), 'resign' => pht('Suggest Reviewers'), ); $form = new AphrontFormView(); $form ->setWorkflow(true) ->setUser($this->user) ->setAction($this->actionURI) ->addHiddenInput('revision_id', $revision->getID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Action')) ->setName('action') ->setValue($action) ->setID('comment-action') ->setOptions($this->actions)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel($enable_reviewers ? $add_reviewers_labels[$action] : $add_reviewers_labels['add_reviewers']) ->setName('reviewers') ->setControlID('add-reviewers') ->setControlStyle($enable_reviewers ? null : 'display: none') ->setID('add-reviewers-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Add CCs')) ->setName('ccs') ->setControlID('add-ccs') ->setControlStyle($enable_ccs ? null : 'display: none') ->setID('add-ccs-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setName('comment') ->setID('comment-content') ->setLabel(pht('Comment')) ->setValue($this->draft ? $this->draft->getDraft() : null) ->setUser($this->user)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($is_serious ? pht('Submit') : pht('Clowncopterize'))); Javelin::initBehavior( 'differential-add-reviewers-and-ccs', array( 'dynamic' => array( 'add-reviewers-tokenizer' => array( 'actions' => array( 'request_review' => 1, 'add_reviewers' => 1, 'resign' => 1, ), 'src' => '/typeahead/common/usersorprojects/', 'value' => $this->reviewers, 'row' => 'add-reviewers', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'labels' => $add_reviewers_labels, 'placeholder' => pht('Type a user or project name...'), ), 'add-ccs-tokenizer' => array( 'actions' => array('add_ccs' => 1), 'src' => '/typeahead/common/mailable/', 'value' => $this->ccs, 'row' => 'add-ccs', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'placeholder' => pht('Type a user or mailing list...'), ), ), 'select' => 'comment-action', )); $diff = $revision->loadActiveDiff(); $warnings = mpull($this->auxFields, 'renderWarningBoxForRevisionAccept'); Javelin::initBehavior( 'differential-accept-with-errors', array( 'select' => 'comment-action', 'warnings' => 'warnings', )); $rev_id = $revision->getID(); Javelin::initBehavior( 'differential-feedback-preview', array( 'uri' => '/differential/comment/preview/'.$rev_id.'/', 'preview' => 'comment-preview', 'action' => 'comment-action', 'content' => 'comment-content', 'previewTokenizers' => array( 'reviewers' => 'add-reviewers-tokenizer', 'ccs' => 'add-ccs-tokenizer', ), 'inlineuri' => '/differential/comment/inline/preview/'.$rev_id.'/', 'inline' => 'inline-comment-preview', )); $warning_container = array(); foreach ($warnings as $warning) { if ($warning) { $warning_container[] = $warning->render(); } } $header = id(new PHUIHeaderView()) ->setHeader($is_serious ? pht('Add Comment') : pht('Leap Into Action')); $anchor = id(new PhabricatorAnchorView()) ->setAnchorName('comment') ->setNavigationMarker(true); $warn = phutil_tag('div', array('id' => 'warnings'), $warning_container); $loading = phutil_tag( 'span', array('class' => 'aphront-panel-preview-loading-text'), pht('Loading comment preview...')); $preview = phutil_tag_div( 'aphront-panel-preview aphront-panel-flush', array( phutil_tag('div', array('id' => 'comment-preview'), $loading), phutil_tag('div', array('id' => 'inline-comment-preview')), )); $comment_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($anchor) ->appendChild($warn) ->appendChild($form); return array($comment_box, $preview); } } diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index 029bd0a2a0..0e9a1711f3 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -1,164 +1,164 @@ changeset = $changeset; return $this; } public function addButton($button) { $this->buttons[] = $button; return $this; } public function setEditable($editable) { $this->editable = $editable; return $this; } public function setSymbolIndex($symbol_index) { $this->symbolIndex = $symbol_index; return $this; } public function getID() { if (!$this->id) { $this->id = celerity_generate_unique_node_id(); } return $this->id; } public function setVsChangesetID($vs_changeset_id) { $this->vsChangesetID = $vs_changeset_id; return $this; } public function getVsChangesetID() { return $this->vsChangesetID; } public function getFileIcon($filename) { $path_info = pathinfo($filename); $extension = idx($path_info, 'extension'); switch ($extension) { case 'psd': case 'ai': $icon = 'preview'; break; case 'conf': $icon = 'wrench'; break; case 'wav': case 'mp3': case 'aiff': $icon = 'music'; break; case 'm4v': case 'mov': $icon = 'film'; break; case 'sql'; case 'db': case 'csv': $icon = 'data'; break; case 'ics': $icon = 'calendar'; break; case 'zip': case 'tar': case 'bz': case 'tgz': case 'gz': $icon = 'zip'; break; case 'png': case 'jpg': case 'bmp': case 'gif': $icon = 'image'; break; default: $icon = 'file'; break; } return $icon; } public function render() { - require_celerity_resource('differential-changeset-view-css'); - require_celerity_resource('syntax-highlighting-css'); + $this->requireResource('differential-changeset-view-css'); + $this->requireResource('syntax-highlighting-css'); Javelin::initBehavior('phabricator-oncopy', array()); $changeset = $this->changeset; $class = 'differential-changeset'; if (!$this->editable) { $class .= ' differential-changeset-immutable'; } $buttons = null; if ($this->buttons) { $buttons = phutil_tag( 'div', array( 'class' => 'differential-changeset-buttons', ), $this->buttons); } $id = $this->getID(); if ($this->symbolIndex) { Javelin::initBehavior( 'repository-crossreference', array( 'container' => $id, ) + $this->symbolIndex); } $display_filename = $changeset->getDisplayFilename(); $display_icon = $this->getFileIcon($display_filename); $icon = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon($display_icon); return javelin_tag( 'div', array( 'sigil' => 'differential-changeset', 'meta' => array( 'left' => nonempty( $this->getVsChangesetID(), $this->changeset->getID()), 'right' => $this->changeset->getID(), ), 'class' => $class, 'id' => $id, ), array( id(new PhabricatorAnchorView()) ->setAnchorName($changeset->getAnchorName()) ->setNavigationMarker(true) ->render(), $buttons, phutil_tag('h1', array( 'class' => 'differential-file-icon-header'), array( $icon, $display_filename)), phutil_tag('div', array('style' => 'clear: both'), ''), $this->renderChildren(), )); } } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 17ed42eac9..069fd2bc4b 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -1,348 +1,348 @@ title = $title; return $this; } private function getTitle() { return $this->title; } public function setBranch($branch) { $this->branch = $branch; return $this; } private function getBranch() { return $this->branch; } public function setChangesets($changesets) { $this->changesets = $changesets; return $this; } public function setVisibleChangesets($visible_changesets) { $this->visibleChangesets = $visible_changesets; return $this; } public function setInlineCommentControllerURI($uri) { $this->inlineURI = $uri; return $this; } public function setRepository(PhabricatorRepository $repository) { $this->repository = $repository; return $this; } public function setDiff(DifferentialDiff $diff) { $this->diff = $diff; return $this; } public function setRenderingReferences(array $references) { $this->references = $references; return $this; } public function setSymbolIndexes(array $indexes) { $this->symbolIndexes = $indexes; return $this; } public function setRenderURI($render_uri) { $this->renderURI = $render_uri; return $this; } public function setWhitespace($whitespace) { $this->whitespace = $whitespace; return $this; } public function setVsMap(array $vs_map) { $this->vsMap = $vs_map; return $this; } public function getVsMap() { return $this->vsMap; } public function setStandaloneURI($uri) { $this->standaloneURI = $uri; return $this; } public function setRawFileURIs($l, $r) { $this->leftRawFileURI = $l; $this->rightRawFileURI = $r; return $this; } public function render() { - require_celerity_resource('differential-changeset-view-css'); + $this->requireResource('differential-changeset-view-css'); $changesets = $this->changesets; Javelin::initBehavior('differential-toggle-files', array( 'pht' => array( 'undo' => pht('Undo'), 'collapsed' => pht('This file content has been collapsed.')) )); Javelin::initBehavior( 'differential-dropdown-menus', array( 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show Entire File' => pht('Show Entire File'), 'Entire File Shown' => pht('Entire File Shown'), "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), 'Expand File' => pht('Expand File'), 'Collapse File' => pht('Collapse File'), 'Browse in Diffusion' => pht('Browse in Diffusion'), 'View Standalone' => pht('View Standalone'), 'Show Raw File (Left)' => pht('Show Raw File (Left)'), 'Show Raw File (Right)' => pht('Show Raw File (Right)'), 'Configure Editor' => pht('Configure Editor'), ), )); $output = array(); $mapping = array(); foreach ($changesets as $key => $changeset) { $file = $changeset->getFilename(); $class = 'differential-changeset'; if (!$this->inlineURI) { $class .= ' differential-changeset-noneditable'; } $ref = $this->references[$key]; $detail = new DifferentialChangesetDetailView(); $view_options = $this->renderViewOptionsDropdown( $detail, $ref, $changeset); $detail->setChangeset($changeset); $detail->addButton($view_options); $detail->setSymbolIndex(idx($this->symbolIndexes, $key)); $detail->setVsChangesetID(idx($this->vsMap, $changeset->getID())); $detail->setEditable(true); $uniq_id = 'diff-'.$changeset->getAnchorName(); if (isset($this->visibleChangesets[$key])) { $load = 'Loading...'; $mapping[$uniq_id] = $ref; } else { $load = javelin_tag( 'a', array( 'href' => '#'.$uniq_id, 'meta' => array( 'id' => $uniq_id, 'ref' => $ref, 'kill' => true, ), 'sigil' => 'differential-load', 'mustcapture' => true, ), pht('Load')); } $detail->appendChild( phutil_tag( 'div', array( 'id' => $uniq_id, ), phutil_tag('div', array('class' => 'differential-loading'), $load))); $output[] = $detail->render(); } - require_celerity_resource('aphront-tooltip-css'); + $this->requireResource('aphront-tooltip-css'); - Javelin::initBehavior('differential-populate', array( + $this->initBehavior('differential-populate', array( 'registry' => $mapping, 'whitespace' => $this->whitespace, 'uri' => $this->renderURI, )); - Javelin::initBehavior('differential-show-more', array( + $this->initBehavior('differential-show-more', array( 'uri' => $this->renderURI, 'whitespace' => $this->whitespace, )); - Javelin::initBehavior('differential-comment-jump', array()); + $this->initBehavior('differential-comment-jump', array()); if ($this->inlineURI) { $undo_templates = $this->renderUndoTemplates(); Javelin::initBehavior('differential-edit-inline-comments', array( 'uri' => $this->inlineURI, 'undo_templates' => $undo_templates, 'stage' => 'differential-review-stage', )); } $header = id(new PHUIHeaderView()) ->setHeader($this->getTitle()); $content = phutil_tag( 'div', array( 'class' => 'differential-review-stage', 'id' => 'differential-review-stage', ), $output); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($content); return $object_box; } /** * Render the "Undo" markup for the inline comment undo feature. */ private function renderUndoTemplates() { $link = javelin_tag( 'a', array( 'href' => '#', 'sigil' => 'differential-inline-comment-undo', ), pht('Undo')); $div = phutil_tag( 'div', array( 'class' => 'differential-inline-undo', ), array('Changes discarded. ', $link)); return array( 'l' => phutil_tag('table', array(), phutil_tag('tr', array(), array( phutil_tag('th', array()), phutil_tag('td', array(), $div), phutil_tag('th', array()), phutil_tag('td', array('colspan' => 3)), ))), 'r' => phutil_tag('table', array(), phutil_tag('tr', array(), array( phutil_tag('th', array()), phutil_tag('td', array()), phutil_tag('th', array()), phutil_tag('td', array('colspan' => 3), $div), ))), ); } private function renderViewOptionsDropdown( DifferentialChangesetDetailView $detail, $ref, DifferentialChangeset $changeset) { $meta = array(); $qparams = array( 'ref' => $ref, 'whitespace' => $this->whitespace, ); if ($this->standaloneURI) { $uri = new PhutilURI($this->standaloneURI); $uri->setQueryParams($uri->getQueryParams() + $qparams); $meta['standaloneURI'] = (string)$uri; } $repository = $this->repository; if ($repository) { try { $meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath( $this->user, $changeset->getAbsoluteRepositoryPath($repository, $this->diff), idx($changeset->getMetadata(), 'line:first'), $this->getBranch()); } catch (DiffusionSetupException $e) { // Ignore } } $change = $changeset->getChangeType(); if ($this->leftRawFileURI) { if ($change != DifferentialChangeType::TYPE_ADD) { $uri = new PhutilURI($this->leftRawFileURI); $uri->setQueryParams($uri->getQueryParams() + $qparams); $meta['leftURI'] = (string)$uri; } } if ($this->rightRawFileURI) { if ($change != DifferentialChangeType::TYPE_DELETE && $change != DifferentialChangeType::TYPE_MULTICOPY) { $uri = new PhutilURI($this->rightRawFileURI); $uri->setQueryParams($uri->getQueryParams() + $qparams); $meta['rightURI'] = (string)$uri; } } $user = $this->user; if ($user && $repository) { $path = ltrim( $changeset->getAbsoluteRepositoryPath($repository, $this->diff), '/'); $line = idx($changeset->getMetadata(), 'line:first', 1); $callsign = $repository->getCallsign(); $editor_link = $user->loadEditorLink($path, $line, $callsign); if ($editor_link) { $meta['editor'] = $editor_link; } else { $meta['editorConfigure'] = '/settings/panel/display/'; } } $meta['containerID'] = $detail->getID(); $caret = phutil_tag('span', array('class' => 'caret'), ''); return javelin_tag( 'a', array( 'class' => 'button grey small dropdown', 'meta' => $meta, 'href' => idx($meta, 'detailURI', '#'), 'target' => '_blank', 'sigil' => 'differential-view-options', ), array(pht('View Options'), $caret)); } } diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php index 84fae42243..74391ef5f9 100644 --- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php +++ b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php @@ -1,309 +1,309 @@ changesets = $changesets; return $this; } public function setVisibleChangesets($visible_changesets) { $this->visibleChangesets = $visible_changesets; return $this; } public function setRenderingReferences(array $references) { $this->references = $references; return $this; } public function setRepository(PhabricatorRepository $repository) { $this->repository = $repository; return $this; } public function setDiff(DifferentialDiff $diff) { $this->diff = $diff; return $this; } public function setUnitTestData($unit_test_data) { $this->unitTestData = $unit_test_data; return $this; } public function setRevisionID($revision_id) { $this->revisionID = $revision_id; return $this; } public function setWhitespace($whitespace) { $this->whitespace = $whitespace; return $this; } public function render() { - require_celerity_resource('differential-core-view-css'); - require_celerity_resource('differential-table-of-contents-css'); + $this->requireResource('differential-core-view-css'); + $this->requireResource('differential-table-of-contents-css'); $rows = array(); $coverage = array(); if ($this->unitTestData) { $coverage_by_file = array(); foreach ($this->unitTestData as $result) { $test_coverage = idx($result, 'coverage'); if (!$test_coverage) { continue; } foreach ($test_coverage as $file => $results) { $coverage_by_file[$file][] = $results; } } foreach ($coverage_by_file as $file => $coverages) { $coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages); } } $changesets = $this->changesets; $paths = array(); foreach ($changesets as $id => $changeset) { $type = $changeset->getChangeType(); $ftype = $changeset->getFileType(); $ref = idx($this->references, $id); $display_file = $changeset->getDisplayFilename(); $meta = null; if (DifferentialChangeType::isOldLocationChangeType($type)) { $away = $changeset->getAwayPaths(); if (count($away) > 1) { $meta = array(); if ($type == DifferentialChangeType::TYPE_MULTICOPY) { $meta[] = pht('Deleted after being copied to multiple locations:'); } else { $meta[] = pht('Copied to multiple locations:'); } foreach ($away as $path) { $meta[] = $path; } $meta = phutil_implode_html(phutil_tag('br'), $meta); } else { if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { $display_file = $this->renderRename( $display_file, reset($away), "\xE2\x86\x92"); } else { $meta = pht('Copied to %s', reset($away)); } } } else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) { $old_file = $changeset->getOldFile(); $display_file = $this->renderRename( $display_file, $old_file, "\xE2\x86\x90"); } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { $meta = pht('Copied from %s', $changeset->getOldFile()); } $link = $this->renderChangesetLink($changeset, $ref, $display_file); $line_count = $changeset->getAffectedLineCount(); if ($line_count == 0) { $lines = null; } else { $lines = ' '.pht('(%d line(s))', $line_count); } $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); $chartitle = DifferentialChangeType::getFullNameForChangeType($type); $desc = DifferentialChangeType::getShortNameForFileType($ftype); if ($desc) { $desc = '('.$desc.')'; } $pchar = ($changeset->getOldProperties() === $changeset->getNewProperties()) ? null : phutil_tag('span', array('title' => pht('Properties Changed')), 'M') ; $fname = $changeset->getFilename(); $cov = $this->renderCoverage($coverage, $fname); if ($cov === null) { $mcov = $cov = phutil_tag('em', array(), '-'); } else { $mcov = phutil_tag( 'div', array( 'id' => 'differential-mcoverage-'.md5($fname), 'class' => 'differential-mcoverage-loading', ), (isset($this->visibleChangesets[$id]) ? 'Loading...' : '?')); } $rows[] = phutil_tag('tr', array(), array( phutil_tag( 'td', array('class' => 'differential-toc-char', 'title' => $chartitle), $char), phutil_tag('td', array('class' => 'differential-toc-prop'), $pchar), phutil_tag('td', array('class' => 'differential-toc-ftype'), $desc), phutil_tag( 'td', array('class' => 'differential-toc-file'), array($link, $lines)), phutil_tag('td', array('class' => 'differential-toc-cov'), $cov), phutil_tag('td', array('class' => 'differential-toc-mcov'), $mcov), )); if ($meta) { $rows[] = phutil_tag('tr', array(), array( phutil_tag('td', array('colspan' => 3)), phutil_tag('td', array('class' => 'differential-toc-meta'), $meta), )); } if ($this->diff && $this->repository) { $paths[] = $changeset->getAbsoluteRepositoryPath($this->repository, $this->diff); } } $editor_link = null; if ($paths && $this->user) { $editor_link = $this->user->loadEditorLink( $paths, 1, // line number $this->repository->getCallsign()); if ($editor_link) { $editor_link = phutil_tag( 'a', array( 'href' => $editor_link, 'class' => 'button differential-toc-edit-all', ), pht('Open All in Editor')); } } $reveal_link = javelin_tag( 'a', array( 'sigil' => 'differential-reveal-all', 'mustcapture' => true, 'class' => 'button differential-toc-reveal-all', ), pht('Show All Context')); $buttons = phutil_tag('tr', array(), phutil_tag('td', array('colspan' => 7), array($editor_link, $reveal_link))); $content = hsprintf( '%s'. '
'. ''. ''. ''. ''. ''. ''. ''. ''. ''. '%s%s'. '
Path%s%s
'. '
', id(new PhabricatorAnchorView()) ->setAnchorName('toc') ->setNavigationMarker(true) ->render(), pht('Coverage (All)'), pht('Coverage (Touched)'), phutil_implode_html("\n", $rows), $buttons); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Table of Contents')) ->appendChild($content); } private function renderRename($display_file, $other_file, $arrow) { $old = explode('/', $display_file); $new = explode('/', $other_file); $start = count($old); foreach ($old as $index => $part) { if (!isset($new[$index]) || $part != $new[$index]) { $start = $index; break; } } $end = count($old); foreach (array_reverse($old) as $from_end => $part) { $index = count($new) - $from_end - 1; if (!isset($new[$index]) || $part != $new[$index]) { $end = $from_end; break; } } $rename = '{'. implode('/', array_slice($old, $start, count($old) - $end - $start)). ' '.$arrow.' '. implode('/', array_slice($new, $start, count($new) - $end - $start)). '}'; array_splice($new, $start, count($new) - $end - $start, $rename); return implode('/', $new); } private function renderCoverage(array $coverage, $file) { $info = idx($coverage, $file); if (!$info) { return null; } $not_covered = substr_count($info, 'U'); $covered = substr_count($info, 'C'); if (!$not_covered && !$covered) { return null; } return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered))); } private function renderChangesetLink( DifferentialChangeset $changeset, $ref, $display_file) { return javelin_tag( 'a', array( 'href' => '#'.$changeset->getAnchorName(), 'meta' => array( 'id' => 'diff-'.$changeset->getAnchorName(), 'ref' => $ref, ), 'sigil' => 'differential-load', ), $display_file); } } diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index 4321807139..d4fbe45431 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -1,145 +1,145 @@ localCommits = $local_commits; return $this; } public function render() { $user = $this->user; if (!$user) { throw new Exception("Call setUser() before render()-ing this view."); } $local = $this->localCommits; if (!$local) { return null; } - require_celerity_resource('differential-local-commits-view-css'); + $this->requireResource('differential-local-commits-view-css'); $has_tree = false; $has_local = false; foreach ($local as $commit) { if (idx($commit, 'tree')) { $has_tree = true; } if (idx($commit, 'local')) { $has_local = true; } } $rows = array(); $highlight = true; foreach ($local as $commit) { if ($highlight) { $class = 'alt'; $highlight = false; } else { $class = ''; $highlight = true; } $row = array(); if (idx($commit, 'commit')) { $commit_hash = self::formatCommit($commit['commit']); } else if (isset($commit['rev'])) { $commit_hash = self::formatCommit($commit['rev']); } else { $commit_hash = null; } $row[] = phutil_tag('td', array(), $commit_hash); if ($has_tree) { $tree = idx($commit, 'tree'); $tree = self::formatCommit($tree); $row[] = phutil_tag('td', array(), $tree); } if ($has_local) { $local_rev = idx($commit, 'local', null); $row[] = phutil_tag('td', array(), $local_rev); } $parents = idx($commit, 'parents', array()); foreach ($parents as $k => $parent) { if (is_array($parent)) { $parent = idx($parent, 'rev'); } $parents[$k] = self::formatCommit($parent); } $parents = phutil_implode_html(phutil_tag('br'), $parents); $row[] = phutil_tag('td', array(), $parents); $author = nonempty( idx($commit, 'user'), idx($commit, 'author')); $row[] = phutil_tag('td', array(), $author); $message = idx($commit, 'message'); $summary = idx($commit, 'summary'); $summary = phutil_utf8_shorten($summary, 80); $view = new AphrontMoreView(); $view->setSome($summary); if ($message && (trim($summary) != trim($message))) { $view->setMore(phutil_escape_html_newlines($message)); } $row[] = phutil_tag( 'td', array( 'class' => 'summary', ), $view->render()); $date = nonempty( idx($commit, 'date'), idx($commit, 'time')); if ($date) { $date = phabricator_datetime($date, $user); } $row[] = phutil_tag('td', array(), $date); $rows[] = phutil_tag('tr', array('class' => $class), $row); } $headers = array(); $headers[] = phutil_tag('th', array(), pht('Commit')); if ($has_tree) { $headers[] = phutil_tag('th', array(), pht('Tree')); } if ($has_local) { $headers[] = phutil_tag('th', array(), pht('Local')); } $headers[] = phutil_tag('th', array(), pht('Parents')); $headers[] = phutil_tag('th', array(), pht('Author')); $headers[] = phutil_tag('th', array(), pht('Summary')); $headers[] = phutil_tag('th', array(), pht('Date')); $headers = phutil_tag('tr', array(), $headers); $content = phutil_tag_div('differential-panel', phutil_tag( 'table', array('class' => 'differential-local-commits-table'), array($headers, phutil_implode_html("\n", $rows)))); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Local Commits')) ->appendChild($content); } private static function formatCommit($commit) { return substr($commit, 0, 12); } } diff --git a/src/applications/differential/view/DifferentialResultsTableView.php b/src/applications/differential/view/DifferentialResultsTableView.php index f2be210728..226c68484c 100644 --- a/src/applications/differential/view/DifferentialResultsTableView.php +++ b/src/applications/differential/view/DifferentialResultsTableView.php @@ -1,115 +1,115 @@ rows = $rows; return $this; } public function setShowMoreString($show_more_string) { $this->showMoreString = $show_more_string; return $this; } public function render() { $rows = array(); $any_hidden = false; foreach ($this->rows as $row) { $style = idx($row, 'style'); switch ($style) { case 'section': $cells = phutil_tag( 'th', array( 'colspan' => 2, ), idx($row, 'name')); break; default: $name = phutil_tag( 'th', array( ), idx($row, 'name')); $value = phutil_tag( 'td', array( ), idx($row, 'value')); $cells = array($name, $value); break; } $show = idx($row, 'show'); $rows[] = javelin_tag( 'tr', array( 'style' => $show ? null : 'display: none', 'sigil' => $show ? null : 'differential-results-row-toggle', 'class' => 'differential-results-row-'.$style, ), $cells); if (!$show) { $any_hidden = true; } } if ($any_hidden) { $show_more = javelin_tag( 'a', array( 'href' => '#', 'mustcapture' => true, ), $this->showMoreString); $hide_more = javelin_tag( 'a', array( 'href' => '#', 'mustcapture' => true, ), 'Hide'); $rows[] = javelin_tag( 'tr', array( 'class' => 'differential-results-row-show', 'sigil' => 'differential-results-row-show', ), phutil_tag('th', array('colspan' => 2), $show_more)); $rows[] = javelin_tag( 'tr', array( 'class' => 'differential-results-row-show', 'sigil' => 'differential-results-row-hide', 'style' => 'display: none', ), phutil_tag('th', array('colspan' => 2), $hide_more)); - Javelin::initBehavior('differential-show-field-details'); + $this->initBehavior('differential-show-field-details'); } - require_celerity_resource('differential-results-table-css'); + $this->requireResource('differential-results-table-css'); return javelin_tag( 'table', array( 'class' => 'differential-results-table', 'sigil' => 'differential-results-table', ), $rows); } } diff --git a/src/applications/differential/view/DifferentialRevisionCommentListView.php b/src/applications/differential/view/DifferentialRevisionCommentListView.php index 86d0c76bfa..b03cee03db 100644 --- a/src/applications/differential/view/DifferentialRevisionCommentListView.php +++ b/src/applications/differential/view/DifferentialRevisionCommentListView.php @@ -1,198 +1,198 @@ comments = $comments; return $this; } public function setInlineComments(array $inline_comments) { assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface'); $this->inlines = $inline_comments; return $this; } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function setChangesets(array $changesets) { assert_instances_of($changesets, 'DifferentialChangeset'); $this->changesets = $changesets; return $this; } public function setTargetDiff(DifferentialDiff $target) { $this->target = $target; return $this; } public function setVersusDiffID($diff_vs) { $this->versusDiffID = $diff_vs; return $this; } public function getID() { if (!$this->id) { $this->id = celerity_generate_unique_node_id(); } return $this->id; } public function render() { - require_celerity_resource('differential-revision-comment-list-css'); + $this->requireResource('differential-revision-comment-list-css'); $engine = new PhabricatorMarkupEngine(); $engine->setViewer($this->user); foreach ($this->comments as $comment) { $comment->giveFacebookSomeArbitraryDiff($this->target); $engine->addObject( $comment, DifferentialComment::MARKUP_FIELD_BODY); } foreach ($this->inlines as $inline) { $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); $inlines = mgroup($this->inlines, 'getCommentID'); $num = 1; $html = array(); foreach ($this->comments as $comment) { $view = new DifferentialRevisionCommentView(); $view->setComment($comment); $view->setUser($this->user); $view->setHandles($this->handles); $view->setMarkupEngine($engine); $view->setInlineComments(idx($inlines, $comment->getID(), array())); $view->setChangesets($this->changesets); $view->setTargetDiff($this->target); $view->setVersusDiffID($this->versusDiffID); if ($comment->getAction() == DifferentialAction::ACTION_SUMMARIZE) { $view->setAnchorName('summary'); } else if ($comment->getAction() == DifferentialAction::ACTION_TESTPLAN) { $view->setAnchorName('test-plan'); } else { $view->setAnchorName('comment-'.$num); $num++; } $html[] = $view->render(); } $objs = array_reverse(array_values($this->comments)); $html = array_reverse(array_values($html)); $user = $this->user; $last_comment = null; // Find the most recent comment by the viewer. foreach ($objs as $position => $comment) { if ($user && ($comment->getAuthorPHID() == $user->getPHID())) { if ($last_comment === null) { $last_comment = $position; } else if ($last_comment == $position - 1) { // If the viewer made several comments in a row, show them all. This // is a spaz rule for epriestley. $last_comment = $position; } } } $header = array(); $hidden = array(); if ($last_comment !== null) { foreach ($objs as $position => $comment) { if (!$comment->getID()) { // These are synthetic comments with summary/test plan information. $header[] = $html[$position]; unset($html[$position]); continue; } if ($position <= $last_comment) { // Always show comments after the viewer's last comment. continue; } if ($position < 3) { // Always show the 3 most recent comments. continue; } $hidden[] = $position; } } if (count($hidden) <= 3) { // Don't hide if there's not much to hide. $hidden = array(); } $header = array_reverse($header); $hidden = array_select_keys($html, $hidden); $visible = array_diff_key($html, $hidden); $hidden = array_reverse($hidden); $visible = array_reverse($visible); if ($hidden) { - Javelin::initBehavior( + $this->initBehavior( 'differential-show-all-comments', array( 'markup' => implode("\n", $hidden), )); $hidden = javelin_tag( 'div', array( 'sigil' => "differential-all-comments-container", ), phutil_tag( 'div', array( 'class' => 'differential-older-comments-are-hidden', ), array( pht( '%s older comments are hidden.', new PhutilNumber(count($hidden))), ' ', javelin_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-show-all-comments', ), pht('Show all comments.')), ))); } else { $hidden = null; } return javelin_tag( 'div', array( 'class' => 'differential-comment-list', 'id' => $this->getID(), ), array_merge($header, array($hidden), $visible)); } } diff --git a/src/applications/differential/view/DifferentialRevisionCommentView.php b/src/applications/differential/view/DifferentialRevisionCommentView.php index 820954dfff..293d93a4a6 100644 --- a/src/applications/differential/view/DifferentialRevisionCommentView.php +++ b/src/applications/differential/view/DifferentialRevisionCommentView.php @@ -1,302 +1,302 @@ comment = $comment; return $this; } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) { $this->markupEngine = $markup_engine; return $this; } public function setPreview($preview) { $this->preview = $preview; return $this; } public function setInlineComments(array $inline_comments) { assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface'); $this->inlines = $inline_comments; return $this; } public function setChangesets(array $changesets) { assert_instances_of($changesets, 'DifferentialChangeset'); // Ship these in sorted by getSortKey() and keyed by ID... or else! $this->changesets = $changesets; return $this; } public function setTargetDiff($target) { $this->target = $target; return $this; } public function setVersusDiffID($diff_vs) { $this->versusDiffID = $diff_vs; return $this; } public function setAnchorName($anchor_name) { $this->anchorName = $anchor_name; return $this; } public function render() { if (!$this->user) { throw new Exception("Call setUser() before rendering!"); } - require_celerity_resource('phabricator-remarkup-css'); - require_celerity_resource('differential-revision-comment-css'); + $this->requireResource('phabricator-remarkup-css'); + $this->requireResource('differential-revision-comment-css'); $comment = $this->comment; $action = $comment->getAction(); $action_class = 'differential-comment-action-'.$action; $info = array(); $content = $comment->getContent(); $hide_comments = true; if (strlen(rtrim($content))) { $hide_comments = false; $content = $this->markupEngine->getOutput( $comment, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); $content = phutil_tag_div('phabricator-remarkup', $content); } $inline_render = $this->renderInlineComments(); if ($inline_render) { $hide_comments = false; } $author = $this->handles[$comment->getAuthorPHID()]; $author_link = $author->renderLink(); $metadata = $comment->getMetadata(); $added_reviewers = idx( $metadata, DifferentialComment::METADATA_ADDED_REVIEWERS, array()); $removed_reviewers = idx( $metadata, DifferentialComment::METADATA_REMOVED_REVIEWERS, array()); $added_ccs = idx( $metadata, DifferentialComment::METADATA_ADDED_CCS, array()); $verb = DifferentialAction::getActionPastTenseVerb($comment->getAction()); $actions = array(); // TODO: i18n switch ($comment->getAction()) { case DifferentialAction::ACTION_ADDCCS: $actions[] = hsprintf( "%s added CCs: %s.", $author_link, $this->renderHandleList($added_ccs)); $added_ccs = null; break; case DifferentialAction::ACTION_ADDREVIEWERS: $actions[] = hsprintf( "%s added reviewers: %s.", $author_link, $this->renderHandleList($added_reviewers)); $added_reviewers = null; break; case DifferentialAction::ACTION_UPDATE: $diff_id = idx($metadata, DifferentialComment::METADATA_DIFF_ID); if ($diff_id) { $diff_link = phutil_tag( 'a', array( 'href' => '/D'.$comment->getRevisionID().'?id='.$diff_id, ), 'Diff #'.$diff_id); $actions[] = hsprintf( "%s updated this revision to %s.", $author_link, $diff_link); } else { $actions[] = hsprintf( "%s %s this revision.", $author_link, $verb); } break; default: $actions[] = hsprintf( "%s %s this revision.", $author_link, $verb); break; } if ($added_reviewers) { $actions[] = hsprintf( "%s added reviewers: %s.", $author_link, $this->renderHandleList($added_reviewers)); } if ($removed_reviewers) { $actions[] = hsprintf( "%s removed reviewers: %s.", $author_link, $this->renderHandleList($removed_reviewers)); } if ($added_ccs) { $actions[] = hsprintf( "%s added CCs: %s.", $author_link, $this->renderHandleList($added_ccs)); } foreach ($actions as $key => $action) { $actions[$key] = phutil_tag('div', array(), $action); } $xaction_view = id(new PhabricatorTransactionView()) ->setUser($this->user) ->setImageURI($author->getImageURI()) ->setContentSource($comment->getContentSource()) ->addClass($action_class) ->setActions($actions); if ($this->preview) { $xaction_view->setIsPreview($this->preview); } else { $xaction_view->setEpoch($comment->getDateCreated()); if ($this->anchorName) { $anchor_text = 'D'.$comment->getRevisionID(). '#'.preg_replace('/^comment-/', '', $this->anchorName); $xaction_view->setAnchor($this->anchorName, $anchor_text); } } if (!$hide_comments) { $xaction_view->appendChild(phutil_tag_div( 'differential-comment-core', array($content, $inline_render))); } return $xaction_view->render(); } private function renderHandleList(array $phids) { $result = array(); foreach ($phids as $phid) { $result[] = $this->handles[$phid]->renderLink(); } return phutil_implode_html(', ', $result); } private function renderInlineComments() { if (!$this->inlines) { return null; } $inlines = $this->inlines; $changesets = $this->changesets; $inlines_by_changeset = mgroup($inlines, 'getChangesetID'); $inlines_by_changeset = array_select_keys( $inlines_by_changeset, array_keys($this->changesets)); $view = new PhabricatorInlineSummaryView(); foreach ($inlines_by_changeset as $changeset_id => $inlines) { $changeset = $changesets[$changeset_id]; $items = array(); foreach ($inlines as $inline) { $on_target = ($this->target) && ($this->target->getID() == $changeset->getDiffID()); $is_visible = false; if ($inline->getIsNewFile()) { // This comment is on the right side of the versus diff, and visible // on the left side of the page. if ($this->versusDiffID) { if ($changeset->getDiffID() == $this->versusDiffID) { $is_visible = true; } } // This comment is on the right side of the target diff, and visible // on the right side of the page. if ($on_target) { $is_visible = true; } } else { // Ths comment is on the left side of the target diff, and visible // on the left side of the page. if (!$this->versusDiffID) { if ($on_target) { $is_visible = true; } } // TODO: We still get one edge case wrong here, when we have a // versus diff and the file didn't exist in the old version. The // comment is visible because we show the left side of the target // diff when there's no corresponding file in the versus diff, but // we incorrectly link it off-page. } $item = array( 'id' => $inline->getID(), 'line' => $inline->getLineNumber(), 'length' => $inline->getLineLength(), 'content' => $this->markupEngine->getOutput( $inline, DifferentialInlineComment::MARKUP_FIELD_BODY), ); if (!$is_visible) { $diff_id = $changeset->getDiffID(); $item['where'] = '(On Diff #'.$diff_id.')'; $item['href'] = 'D'.$this->comment->getRevisionID(). '?id='.$diff_id. '#inline-'.$inline->getID(); } $items[] = $item; } $view->addCommentGroup($changeset->getFilename(), $items); } return $view; } } diff --git a/src/applications/differential/view/DifferentialRevisionDetailView.php b/src/applications/differential/view/DifferentialRevisionDetailView.php index fad95b5ada..7e0f3c5de6 100644 --- a/src/applications/differential/view/DifferentialRevisionDetailView.php +++ b/src/applications/differential/view/DifferentialRevisionDetailView.php @@ -1,177 +1,177 @@ 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 setAuxiliaryFields(array $fields) { assert_instances_of($fields, 'DifferentialFieldSpecification'); $this->auxiliaryFields = $fields; return $this; } public function render() { - require_celerity_resource('differential-core-view-css'); + $this->requireResource('differential-core-view-css'); $revision = $this->revision; $user = $this->getUser(); $header = $this->renderHeader($revision); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($revision) ->setObjectURI($this->getURI()); foreach ($this->getActions() as $action) { $obj = id(new PhabricatorActionView()) ->setIcon(idx($action, 'icon', 'edit')) ->setName($action['name']) ->setHref(idx($action, 'href')) ->setWorkflow(idx($action, 'sigil') == 'workflow') ->setRenderAsForm(!empty($action['instant'])) ->setUser($user) ->setDisabled(idx($action, 'disabled', false)); $actions->addAction($obj); } $properties = id(new PHUIPropertyListView()) ->setUser($user) ->setObject($revision); $status = $revision->getStatus(); $local_vcs = $this->getDiff()->getSourceControlSystem(); $next_step = null; if ($status == ArcanistDifferentialRevisionStatus::ACCEPTED) { switch ($local_vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $bookmark = $this->getDiff()->getBookmark(); $next_step = ($bookmark != '' ? csprintf('arc land %s', $bookmark) : 'arc land'); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $branch = $this->getDiff()->getBranch(); $next_step = ($branch != '' ? csprintf('arc land %s', $branch) : 'arc land'); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $next_step = 'arc commit'; break; } } if ($next_step) { $next_step = phutil_tag('tt', array(), $next_step); $properties->addProperty(pht('Next Step'), $next_step); } foreach ($this->auxiliaryFields as $field) { $value = $field->renderValueForRevisionView(); if ($value !== null) { $label = rtrim($field->renderLabelForRevisionView(), ':'); $properties->addProperty($label, $value); } } $properties->setHasKeyboardShortcuts(true); $properties->setActionList($actions); $properties->invokeWillRenderEvent(); if (strlen($revision->getSummary())) { $properties->addSectionHeader( pht('Summary'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent( PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff()) ->setPreserveLinebreaks(true) ->setContent($revision->getSummary()), 'default', $user)); } if (strlen($revision->getTestPlan())) { $properties->addSectionHeader( pht('Test Plan'), PHUIPropertyListView::ICON_TESTPLAN); $properties->addTextContent( PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff()) ->setPreserveLinebreaks(true) ->setContent($revision->getTestPlan()), 'default', $user)); } $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 PhabricatorTagView()) ->setType(PhabricatorTagView::TYPE_STATE) ->setName($status_name); } } diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 86bab4dbc6..0addfa5b62 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -1,253 +1,253 @@ noDataString = $no_data_string; return $this; } public function setHeader($header) { $this->header = $header; return $this; } public function setFields(array $fields) { assert_instances_of($fields, 'DifferentialFieldSpecification'); $this->fields = $fields; return $this; } public function setRevisions(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $this->revisions = $revisions; return $this; } public function setHighlightAge($bool) { $this->highlightAge = $bool; return $this; } public function getRequiredHandlePHIDs() { $phids = array(); foreach ($this->fields as $field) { foreach ($this->revisions as $revision) { $phids[] = $field->getRequiredHandlePHIDsForRevisionList($revision); } } return array_mergev($phids); } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function loadAssets() { $user = $this->user; if (!$user) { throw new Exception("Call setUser() before loadAssets()!"); } if ($this->revisions === null) { throw new Exception("Call setRevisions() before loadAssets()!"); } $this->flags = id(new PhabricatorFlagQuery()) ->setViewer($user) ->withOwnerPHIDs(array($user->getPHID())) ->withObjectPHIDs(mpull($this->revisions, 'getPHID')) ->execute(); $this->drafts = id(new DifferentialRevisionQuery()) ->setViewer($user) ->withIDs(mpull($this->revisions, 'getID')) ->withDraftRepliesByAuthors(array($user->getPHID())) ->execute(); return $this; } public function render() { $user = $this->user; if (!$user) { throw new Exception("Call setUser() before render()!"); } $fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh'); if ($fresh) { $fresh = PhabricatorCalendarHoliday::getNthBusinessDay( time(), -$fresh); } $stale = PhabricatorEnv::getEnvConfig('differential.days-stale'); if ($stale) { $stale = PhabricatorCalendarHoliday::getNthBusinessDay( time(), -$stale); } - Javelin::initBehavior('phabricator-tooltips', array()); - require_celerity_resource('aphront-tooltip-css'); + $this->initBehavior('phabricator-tooltips', array()); + $this->requireResource('aphront-tooltip-css'); $flagged = mpull($this->flags, null, 'getObjectPHID'); foreach ($this->fields as $field) { $field->setHandles($this->handles); } $list = new PHUIObjectItemListView(); $list->setCards(true); foreach ($this->revisions as $revision) { $item = id(new PHUIObjectItemView()) ->setUser($user); $rev_fields = array(); $icons = array(); $phid = $revision->getPHID(); if (isset($flagged[$phid])) { $flag = $flagged[$phid]; $flag_class = PhabricatorFlagColor::getCSSClass($flag->getColor()); $icons['flag'] = phutil_tag( 'div', array( 'class' => 'phabricator-flag-icon '.$flag_class, ), ''); } if (array_key_exists($revision->getID(), $this->drafts)) { $icons['draft'] = true; } $modified = $revision->getDateModified(); $status = $revision->getStatus(); $show_age = ($fresh || $stale) && $this->highlightAge && !$revision->isClosed(); $object_age = PHUIObjectItemView::AGE_FRESH; foreach ($this->fields as $field) { if ($show_age) { if ($field instanceof DifferentialDateModifiedFieldSpecification) { if ($stale && $modified < $stale) { $object_age = PHUIObjectItemView::AGE_OLD; } else if ($fresh && $modified < $fresh) { $object_age = PHUIObjectItemView::AGE_STALE; } } } $rev_header = $field->renderHeaderForRevisionList(); $rev_fields[$rev_header] = $field ->renderValueForRevisionList($revision); } $status_name = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); if (isset($icons['flag'])) { $item->addHeadIcon($icons['flag']); } $item->setObjectName('D'.$revision->getID()); $item->setHeader(phutil_tag('a', array('href' => '/D'.$revision->getID()), $revision->getTitle())); if (isset($icons['draft'])) { $draft = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon('file-grey') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Unsubmitted Comments'), )); $item->addAttribute($draft); } $item->addAttribute($status_name); // Author $author_handle = $this->handles[$revision->getAuthorPHID()]; $item->addByline(pht('Author: %s', $author_handle->renderLink())); // Reviewers $item->addAttribute(pht('Reviewers: %s', $rev_fields['Reviewers'])); $item->setEpoch($revision->getDateModified(), $object_age); // First remove the fields we already have $count = 7; $rev_fields = array_slice($rev_fields, $count); // Then add each one of them // TODO: Add render-to-foot-icon support foreach ($rev_fields as $header => $field) { $item->addAttribute(pht('%s: %s', $header, $field)); } switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $item->setBarColor('red'); break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $item->setBarColor('green'); break; case ArcanistDifferentialRevisionStatus::CLOSED: $item->setDisabled(true); break; case ArcanistDifferentialRevisionStatus::ABANDONED: $item->setBarColor('black'); break; } $list->addItem($item); } $list->setHeader($this->header); $list->setNoDataString($this->noDataString); return $list; } public static function getDefaultFields(PhabricatorUser $user) { $selector = DifferentialFieldSelector::newSelector(); $fields = $selector->getFieldSpecifications(); foreach ($fields as $key => $field) { $field->setUser($user); if (!$field->shouldAppearOnRevisionList()) { unset($fields[$key]); } } if (!$fields) { throw new Exception( "Phabricator configuration has no fields that appear on the list ". "interface!"); } return $selector->sortFieldsForRevisionList($fields); } } diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index e4a78860df..5029a3bb44 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -1,329 +1,329 @@ diffs = $diffs; return $this; } public function setSelectedVersusDiffID($id) { $this->selectedVersusDiffID = $id; return $this; } public function setSelectedDiffID($id) { $this->selectedDiffID = $id; return $this; } public function setSelectedWhitespace($whitespace) { $this->selectedWhitespace = $whitespace; return $this; } public function render() { - require_celerity_resource('differential-core-view-css'); - require_celerity_resource('differential-revision-history-css'); + $this->requireResource('differential-core-view-css'); + $this->requireResource('differential-revision-history-css'); $data = array( array( 'name' => 'Base', 'id' => null, 'desc' => 'Base', 'age' => null, 'obj' => null, ), ); $seq = 0; foreach ($this->diffs as $diff) { $data[] = array( 'name' => 'Diff '.(++$seq), 'id' => $diff->getID(), 'desc' => $diff->getDescription(), 'age' => $diff->getDateCreated(), 'obj' => $diff, ); } $max_id = $diff->getID(); $idx = 0; $rows = array(); $disable = false; $radios = array(); $last_base = null; foreach ($data as $row) { $diff = $row['obj']; $name = $row['name']; $id = $row['id']; $old_class = null; $new_class = null; if ($id) { $new_checked = ($this->selectedDiffID == $id); $new = javelin_tag( 'input', array( 'type' => 'radio', 'name' => 'id', 'value' => $id, 'checked' => $new_checked ? 'checked' : null, 'sigil' => 'differential-new-radio', )); if ($new_checked) { $new_class = " revhistory-new-now"; $disable = true; } } else { $new = null; } if ($max_id != $id) { $uniq = celerity_generate_unique_node_id(); $old_checked = ($this->selectedVersusDiffID == $id); $old = phutil_tag( 'input', array( 'type' => 'radio', 'name' => 'vs', 'value' => $id, 'id' => $uniq, 'checked' => $old_checked ? 'checked' : null, 'disabled' => $disable ? 'disabled' : null, )); $radios[] = $uniq; if ($old_checked) { $old_class = " revhistory-old-now"; } } else { $old = null; } $desc = $row['desc']; if ($row['age']) { $age = phabricator_datetime($row['age'], $this->getUser()); } else { $age = null; } if (++$idx % 2) { $class = 'alt'; } else { $class = null; } $lint_attrs = array('class' => 'revhistory-star'); $unit_attrs = array('class' => 'revhistory-star'); if ($diff) { $lint = self::renderDiffLintStar($row['obj']); $unit = self::renderDiffUnitStar($row['obj']); $lint_attrs['title'] = self::getDiffLintMessage($diff); $unit_attrs['title'] = self::getDiffUnitMessage($diff); $base = $this->renderBaseRevision($diff); } else { $lint = null; $unit = null; $base = null; } if ($last_base !== null && $base !== $last_base) { // TODO: Render some kind of notice about rebases. } $last_base = $base; $id_link = phutil_tag( 'a', array('href' => '/differential/diff/'.$id.'/'), $id); $rows[] = phutil_tag( 'tr', array('class' => $class), array( phutil_tag('td', array('class' => 'revhistory-name'), $name), phutil_tag('td', array('class' => 'revhistory-id'), $id_link), phutil_tag('td', array('class' => 'revhistory-base'), $base), phutil_tag('td', array('class' => 'revhistory-desc'), $desc), phutil_tag('td', array('class' => 'revhistory-age'), $age), phutil_tag('td', $lint_attrs, $lint), phutil_tag('td', $unit_attrs, $unit), phutil_tag('td', array('class' => 'revhistory-old'.$old_class), $old), phutil_tag('td', array('class' => 'revhistory-new'.$new_class), $new), )); } Javelin::initBehavior( 'differential-diff-radios', array( 'radios' => $radios, )); $options = array( DifferentialChangesetParser::WHITESPACE_IGNORE_FORCE => 'Ignore All', DifferentialChangesetParser::WHITESPACE_IGNORE_ALL => 'Ignore Most', DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING => 'Ignore Trailing', DifferentialChangesetParser::WHITESPACE_SHOW_ALL => 'Show All', ); foreach ($options as $value => $label) { $options[$value] = phutil_tag( 'option', array( 'value' => $value, 'selected' => ($value == $this->selectedWhitespace) ? 'selected' : null, ), $label); } $select = phutil_tag('select', array('name' => 'whitespace'), $options); array_unshift($rows, phutil_tag('tr', array(), array( phutil_tag('th', array(), pht('Diff')), phutil_tag('th', array(), pht('ID')), phutil_tag('th', array(), pht('Base')), phutil_tag('th', array(), pht('Description')), phutil_tag('th', array(), pht('Created')), phutil_tag('th', array(), pht('Lint')), phutil_tag('th', array(), pht('Unit')), ))); $label = pht('Whitespace Changes: %s', $select); $content = phutil_tag_div( 'differential-revision-history differential-panel', phutil_tag( 'form', array('action' => '#toc'), phutil_tag( 'table', array('class' => 'differential-revision-history-table'), array( phutil_implode_html("\n", $rows), phutil_tag('tr', array(), phutil_tag( 'td', array('colspan' => 9, 'class' => 'diff-differ-submit'), array( phutil_tag('label', array(), $label), phutil_tag('button', array(), pht('Show Diff')), ))) )))); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Revision Update History')) ->appendChild($content); } const STAR_NONE = 'none'; const STAR_OKAY = 'okay'; const STAR_WARN = 'warn'; const STAR_FAIL = 'fail'; const STAR_SKIP = 'skip'; public static function renderDiffLintStar(DifferentialDiff $diff) { static $map = array( DifferentialLintStatus::LINT_NONE => self::STAR_NONE, DifferentialLintStatus::LINT_OKAY => self::STAR_OKAY, DifferentialLintStatus::LINT_WARN => self::STAR_WARN, DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL, DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP, DifferentialLintStatus::LINT_POSTPONED => self::STAR_SKIP ); $star = idx($map, $diff->getLintStatus(), self::STAR_FAIL); return self::renderDiffStar($star); } public static function renderDiffUnitStar(DifferentialDiff $diff) { static $map = array( DifferentialUnitStatus::UNIT_NONE => self::STAR_NONE, DifferentialUnitStatus::UNIT_OKAY => self::STAR_OKAY, DifferentialUnitStatus::UNIT_WARN => self::STAR_WARN, DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL, DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP, DifferentialUnitStatus::UNIT_POSTPONED => self::STAR_SKIP, ); $star = idx($map, $diff->getUnitStatus(), self::STAR_FAIL); return self::renderDiffStar($star); } public static function getDiffLintMessage(DifferentialDiff $diff) { switch ($diff->getLintStatus()) { case DifferentialLintStatus::LINT_NONE: return 'No Linters Available'; case DifferentialLintStatus::LINT_OKAY: return 'Lint OK'; case DifferentialLintStatus::LINT_WARN: return 'Lint Warnings'; case DifferentialLintStatus::LINT_FAIL: return 'Lint Errors'; case DifferentialLintStatus::LINT_SKIP: return 'Lint Skipped'; case DifferentialLintStatus::LINT_POSTPONED: return 'Lint Postponed'; } return '???'; } public static function getDiffUnitMessage(DifferentialDiff $diff) { switch ($diff->getUnitStatus()) { case DifferentialUnitStatus::UNIT_NONE: return 'No Unit Test Coverage'; case DifferentialUnitStatus::UNIT_OKAY: return 'Unit Tests OK'; case DifferentialUnitStatus::UNIT_WARN: return 'Unit Test Warnings'; case DifferentialUnitStatus::UNIT_FAIL: return 'Unit Test Errors'; case DifferentialUnitStatus::UNIT_SKIP: return 'Unit Tests Skipped'; case DifferentialUnitStatus::UNIT_POSTPONED: return 'Unit Tests Postponed'; } return '???'; } private static function renderDiffStar($star) { $class = 'diff-star-'.$star; return phutil_tag( 'span', array('class' => $class), "\xE2\x98\x85"); } private function renderBaseRevision(DifferentialDiff $diff) { switch ($diff->getSourceControlSystem()) { case 'git': $base = $diff->getSourceControlBaseRevision(); if (strpos($base, '@') === false) { return substr($base, 0, 7); } else { // The diff is from git-svn $base = explode('@', $base); $base = last($base); return $base; } case 'svn': $base = $diff->getSourceControlBaseRevision(); $base = explode('@', $base); $base = last($base); return $base; default: return null; } } } diff --git a/src/infrastructure/celerity/CelerityResourceMap.php b/src/infrastructure/celerity/CelerityResourceMap.php index ebaa4fb9a6..c9beffc2d3 100644 --- a/src/infrastructure/celerity/CelerityResourceMap.php +++ b/src/infrastructure/celerity/CelerityResourceMap.php @@ -1,239 +1,243 @@ resources = $resources; $map = $resources->loadMap(); $this->symbolMap = idx($map, 'symbols', array()); $this->requiresMap = idx($map, 'requires', array()); $this->packageMap = idx($map, 'packages', array()); $this->nameMap = idx($map, 'names', array()); // We derive these reverse maps at runtime. $this->hashMap = array_flip($this->nameMap); $this->componentMap = array(); foreach ($this->packageMap as $package_name => $symbols) { foreach ($symbols as $symbol) { $this->componentMap[$symbol] = $package_name; } } } public static function getNamedInstance($name) { if (empty(self::$instances[$name])) { $resources_list = CelerityPhysicalResources::getAll(); if (empty($resources_list[$name])) { throw new Exception( pht( 'No resource source exists with name "%s"!', $name)); } $instance = new CelerityResourceMap($resources_list[$name]); self::$instances[$name] = $instance; } return self::$instances[$name]; } public function getPackagedNamesForSymbols(array $symbols) { $resolved = $this->resolveResources($symbols); return $this->packageResources($resolved); } private function resolveResources(array $symbols) { $map = array(); foreach ($symbols as $symbol) { if (!empty($map[$symbol])) { continue; } $this->resolveResource($map, $symbol); } return $map; } private function resolveResource(array &$map, $symbol) { if (empty($this->symbolMap[$symbol])) { throw new Exception( pht( 'Attempting to resolve unknown resource, "%s".', $symbol)); } $hash = $this->symbolMap[$symbol]; $map[$symbol] = $hash; if (isset($this->requiresMap[$hash])) { $requires = $this->requiresMap[$hash]; } else { $requires = array(); } foreach ($requires as $required_symbol) { if (!empty($map[$required_symbol])) { continue; } $this->resolveResource($map, $required_symbol); } } private function packageResources(array $resolved_map) { $packaged = array(); $handled = array(); foreach ($resolved_map as $symbol => $hash) { if (isset($handled[$symbol])) { continue; } if (empty($this->componentMap[$symbol])) { $packaged[] = $this->hashMap[$hash]; } else { $package_name = $this->componentMap[$symbol]; $packaged[] = $package_name; $package_symbols = $this->packageMap[$package_name]; foreach ($package_symbols as $package_symbol) { $handled[$package_symbol] = true; } } } return $packaged; } public function getResourceDataForName($resource_name) { return $this->resources->getResourceData($resource_name); } public function getResourceNamesForPackageName($package_name) { $package_symbols = idx($this->packageMap, $package_name); if (!$package_symbols) { return null; } $resource_names = array(); foreach ($package_symbols as $symbol) { $resource_names[] = $this->hashMap[$this->symbolMap[$symbol]]; } return $resource_names; } /** * Get the epoch timestamp of the last modification time of a symbol. * * @param string Resource symbol to lookup. * @return int Epoch timestamp of last resource modification. */ public function getModifiedTimeForName($name) { if ($this->isPackageResource($name)) { $names = array(); foreach ($this->packageMap[$name] as $symbol) { $names[] = $this->getResourceNameForSymbol($symbol); } } else { $names = array($name); } $mtime = 0; foreach ($names as $name) { $mtime = max($mtime, $this->resources->getResourceModifiedTime($name)); } return $mtime; } /** * Return the absolute URI for the resource associated with a symbol. This * method is fairly low-level and ignores packaging. * * @param string Resource symbol to lookup. * @return string|null Resource URI, or null if the symbol is unknown. */ public function getURIForSymbol($symbol) { $hash = idx($this->symbolMap, $symbol); return $this->getURIForHash($hash); } /** * Return the absolute URI for the resource associated with a resource name. * This method is fairly low-level and ignores packaging. * * @param string Resource name to lookup. * @return string|null Resource URI, or null if the name is unknown. */ public function getURIForName($name) { $hash = idx($this->nameMap, $name); return $this->getURIForHash($hash); } /** * Return the absolute URI for a resource, identified by hash. * This method is fairly low-level and ignores packaging. * * @param string Resource hash to lookup. * @return string|null Resource URI, or null if the hash is unknown. */ private function getURIForHash($hash) { if ($hash === null) { return null; } return $this->resources->getResourceURI($hash, $this->hashMap[$hash]); } /** * Return the resource symbols required by a named resource. * * @param string Resource name to lookup. * @return list|null List of required symbols, or null if the name * is unknown. */ public function getRequiredSymbolsForName($name) { $hash = idx($this->symbolMap, $name); if ($hash === null) { return null; } return idx($this->requiresMap, $hash, array()); } /** * Return the resource name for a given symbol. * * @param string Resource symbol to lookup. * @return string|null Resource name, or null if the symbol is unknown. */ public function getResourceNameForSymbol($symbol) { $hash = idx($this->symbolMap, $symbol); return idx($this->hashMap, $hash); } public function isPackageResource($name) { return isset($this->packageMap[$name]); } + public function getResourceTypeForName($name) { + return $this->resources->getResourceType($name); + } + } diff --git a/src/infrastructure/celerity/CelerityStaticResourceResponse.php b/src/infrastructure/celerity/CelerityStaticResourceResponse.php index b52274f323..21ad591e87 100644 --- a/src/infrastructure/celerity/CelerityStaticResourceResponse.php +++ b/src/infrastructure/celerity/CelerityStaticResourceResponse.php @@ -1,256 +1,304 @@ metadataBlock = (int)$_REQUEST['__metablock__']; } } public function addMetadata($metadata) { $id = count($this->metadata); $this->metadata[$id] = $metadata; return $this->metadataBlock.'_'.$id; } public function getMetadataBlock() { return $this->metadataBlock; } /** * Register a behavior for initialization. NOTE: if $config is empty, * a behavior will execute only once even if it is initialized multiple times. * If $config is nonempty, the behavior will be invoked once for each config. */ - public function initBehavior($behavior, array $config = array()) { - $this->requireResource('javelin-behavior-'.$behavior); + public function initBehavior( + $behavior, + array $config = array(), + $source_name = 'phabricator') { + + $this->requireResource('javelin-behavior-'.$behavior, $source_name); if (empty($this->behaviors[$behavior])) { $this->behaviors[$behavior] = array(); } if ($config) { $this->behaviors[$behavior][] = $config; } return $this; } - public function requireResource($symbol) { - $this->symbols[$symbol] = true; + public function requireResource($symbol, $source_name) { + if (isset($this->symbols[$source_name][$symbol])) { + return $this; + } + + // Verify that the resource exists. + $map = CelerityResourceMap::getNamedInstance($source_name); + $name = $map->getResourceNameForSymbol($symbol); + if ($name === null) { + throw new Exception( + pht( + 'No resource with symbol "%s" exists in source "%s"!', + $symbol, + $source_name)); + } + + $this->symbols[$source_name][$symbol] = true; $this->needsResolve = true; + return $this; } private function resolveResources() { if ($this->needsResolve) { - $map = CelerityResourceMap::getNamedInstance('phabricator'); + $this->packaged = array(); + foreach ($this->symbols as $source_name => $symbols_map) { + $symbols = array_keys($symbols_map); - $symbols = array_keys($this->symbols); - $this->packaged = $map->getPackagedNamesForSymbols($symbols); + $map = CelerityResourceMap::getNamedInstance($source_name); + $packaged = $map->getPackagedNamesForSymbols($symbols); + $this->packaged[$source_name] = $packaged; + } $this->needsResolve = false; } return $this; } public function renderSingleResource($symbol, $source_name) { $map = CelerityResourceMap::getNamedInstance($source_name); $packaged = $map->getPackagedNamesForSymbols(array($symbol)); - return $this->renderPackagedResources($packaged); + return $this->renderPackagedResources($map, $packaged); } public function renderResourcesOfType($type) { $this->resolveResources(); - $resources = array(); - foreach ($this->packaged as $name) { - $resource_type = CelerityResourceTransformer::getResourceType($name); - if ($resource_type == $type) { - $resources[] = $name; + $result = array(); + foreach ($this->packaged as $source_name => $resource_names) { + $map = CelerityResourceMap::getNamedInstance($source_name); + + $resources_of_type = array(); + foreach ($resource_names as $resource_name) { + $resource_type = $map->getResourceTypeForName($resource_name); + if ($resource_type == $type) { + $resources_of_type[] = $resource_name; + } } + + $result[] = $this->renderPackagedResources($map, $resources_of_type); } - return $this->renderPackagedResources($resources); + return phutil_implode_html('', $result); } - private function renderPackagedResources(array $resources) { + private function renderPackagedResources( + CelerityResourceMap $map, + array $resources) { + $output = array(); foreach ($resources as $name) { if (isset($this->hasRendered[$name])) { continue; } $this->hasRendered[$name] = true; - $output[] = $this->renderResource($name); - $output[] = "\n"; + $output[] = $this->renderResource($map, $name); } - return phutil_implode_html('', $output); + + return $output; } - private function renderResource($name) { - $uri = $this->getURI($name); - $type = CelerityResourceTransformer::getResourceType($name); + private function renderResource( + CelerityResourceMap $map, + $name) { + + $uri = $this->getURI($map, $name); + $type = $map->getResourceTypeForName($name); + switch ($type) { case 'css': return phutil_tag( 'link', array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => $uri, )); case 'js': return phutil_tag( 'script', array( 'type' => 'text/javascript', 'src' => $uri, ), ''); } - throw new Exception("Unable to render resource."); + + throw new Exception( + pht( + 'Unable to render resource "%s", which has unknown type "%s".', + $name, + $type)); } public function renderHTMLFooter() { $data = array(); if ($this->metadata) { $json_metadata = AphrontResponse::encodeJSONForHTTPResponse( $this->metadata); $this->metadata = array(); } else { $json_metadata = '{}'; } // Even if there is no metadata on the page, Javelin uses the mergeData() // call to start dispatching the event queue. $data[] = 'JX.Stratcom.mergeData('.$this->metadataBlock.', '. $json_metadata.');'; $onload = array(); if ($this->behaviors) { $behaviors = $this->behaviors; $this->behaviors = array(); $higher_priority_names = array( 'refresh-csrf', 'aphront-basic-tokenizer', 'dark-console', 'history-install', ); $higher_priority_behaviors = array_select_keys( $behaviors, $higher_priority_names); foreach ($higher_priority_names as $name) { unset($behaviors[$name]); } $behavior_groups = array( $higher_priority_behaviors, $behaviors); foreach ($behavior_groups as $group) { if (!$group) { continue; } $group_json = AphrontResponse::encodeJSONForHTTPResponse( $group); $onload[] = 'JX.initBehaviors('.$group_json.')'; } } if ($onload) { foreach ($onload as $func) { $data[] = 'JX.onload(function(){'.$func.'});'; } } if ($data) { $data = implode("\n", $data); return self::renderInlineScript($data); } else { return ''; } } public static function renderInlineScript($data) { if (stripos($data, '') !== false) { throw new Exception( 'Literal is not allowed inside inline script.'); } if (strpos($data, ' because it is ignored by HTML parsers. We // would need to send the document with XHTML content type. return phutil_tag( 'script', array('type' => 'text/javascript'), phutil_safe_html($data)); } public function buildAjaxResponse($payload, $error = null) { $response = array( 'error' => $error, 'payload' => $payload, ); if ($this->metadata) { $response['javelin_metadata'] = $this->metadata; $this->metadata = array(); } if ($this->behaviors) { $response['javelin_behaviors'] = $this->behaviors; $this->behaviors = array(); } $this->resolveResources(); $resources = array(); - foreach ($this->packaged as $resource) { - $resources[] = $this->getURI($resource); + foreach ($this->packaged as $source_name => $resource_names) { + $map = CelerityResourceMap::getNamedInstance($source_name); + foreach ($resource_names as $resource_name) { + $resources[] = $this->getURI($map, $resource_name); + } } if ($resources) { $response['javelin_resources'] = $resources; } return $response; } - private function getURI($name) { - $map = CelerityResourceMap::getNamedInstance('phabricator'); + private function getURI( + CelerityResourceMap $map, + $name) { + $uri = $map->getURIForName($name); // In developer mode, we dump file modification times into the URI. When a // page is reloaded in the browser, any resources brought in by Ajax calls // do not trigger revalidation, so without this it's very difficult to get // changes to Ajaxed-in CSS to work (you must clear your cache or rerun // the map script). In production, we can assume the map script gets run // after changes, and safely skip this. if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { $mtime = $map->getModifiedTimeForName($name); $uri = preg_replace('@^/res/@', '/res/'.$mtime.'T/', $uri); } return PhabricatorEnv::getCDNURI($uri); } } diff --git a/src/infrastructure/celerity/api.php b/src/infrastructure/celerity/api.php index 8b04b49fd3..c10b7a1d40 100644 --- a/src/infrastructure/celerity/api.php +++ b/src/infrastructure/celerity/api.php @@ -1,61 +1,61 @@ requireResource($symbol); + $response->requireResource($symbol, $source_name); } /** * Generate a node ID which is guaranteed to be unique for the current page, * even across Ajax requests. You should use this method to generate IDs for * nodes which require a uniqueness guarantee. * * @return string A string appropriate for use as an 'id' attribute on a DOM * node. It is guaranteed to be unique for the current page, even * if the current request is a subsequent Ajax request. * * @group celerity */ function celerity_generate_unique_node_id() { static $uniq = 0; $response = CelerityAPI::getStaticResourceResponse(); $block = $response->getMetadataBlock(); return 'UQ'.$block.'_'.($uniq++); } /** * Get the versioned URI for a raw resource, like an image. * * @param string Path to the raw image. * @return string Versioned path to the image, if one is available. * * @group celerity */ function celerity_get_resource_uri($resource, $source = 'phabricator') { $map = CelerityResourceMap::getNamedInstance($source); $uri = $map->getURIForName($resource); if ($uri) { return $uri; } return $resource; } diff --git a/src/view/AphrontView.php b/src/view/AphrontView.php index 33bddb701e..788ea50ab3 100644 --- a/src/view/AphrontView.php +++ b/src/view/AphrontView.php @@ -1,145 +1,162 @@ user = $user; return $this; } /** * @task config */ protected function getUser() { return $this->user; } /* -( Managing Children )-------------------------------------------------- */ /** * Test if this View accepts children. * * By default, views accept children, but subclases may override this method * to prevent children from being appended. Doing so will cause * @{method:appendChild} to throw exceptions instead of appending children. * * @return bool True if the View should accept children. * @task children */ protected function canAppendChild() { return true; } /** * Append a child to the list of children. * * This method will only work if the view supports children, which is * determined by @{method:canAppendChild}. * * @param wild Something renderable. * @return this */ final public function appendChild($child) { if (!$this->canAppendChild()) { $class = get_class($this); throw new Exception( pht("View '%s' does not support children.", $class)); } $this->children[] = $child; return $this; } /** * Produce children for rendering. * * Historically, this method reduced children to a string representation, * but it no longer does. * * @return wild Renderable children. * @task */ final protected function renderChildren() { return $this->children; } /** * Test if an element has no children. * * @return bool True if this element has children. * @task children */ final public function hasChildren() { if ($this->children) { $this->children = $this->reduceChildren($this->children); } return (bool)$this->children; } /** * Reduce effectively-empty lists of children to be actually empty. This * recursively removes `null`, `''`, and `array()` from the list of children * so that @{method:hasChildren} can more effectively align with expectations. * * NOTE: Because View children are not rendered, a View which renders down * to nothing will not be reduced by this method. * * @param list Renderable children. * @return list Reduced list of children. * @task children */ private function reduceChildren(array $children) { foreach ($children as $key => $child) { if ($child === null) { unset($children[$key]); } else if ($child === '') { unset($children[$key]); } else if (is_array($child)) { $child = $this->reduceChildren($child); if ($child) { $children[$key] = $child; } else { unset($children[$key]); } } } return $children; } + public function getDefaultResourceSource() { + return 'phabricator'; + } + + public function requireResource($symbol) { + $response = CelerityAPI::getStaticResourceResponse(); + $response->requireResource($symbol, $this->getDefaultResourceSource()); + return $this; + } + + public function initBehavior($name, $config = array()) { + Javelin::initBehavior( + $name, + $config, + $this->getDefaultResourceSource()); + } + /* -( Rendering )---------------------------------------------------------- */ abstract public function render(); /* -( PhutilSafeHTMLProducerInterface )------------------------------------ */ public function producePhutilSafeHTML() { return $this->render(); } }