diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index e91884ef5..6d1750683 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,384 +1,384 @@ getResourceURIMapRules() + array( '/(?:(?P(?:jump))/)?' => 'PhabricatorDirectoryMainController', '/typeahead/' => array( 'common/(?P\w+)/' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/login/' => array( '' => 'PhabricatorLoginController', 'email/' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/' => 'PhabricatorEmailTokenController', 'refresh/' => 'PhabricatorRefreshCSRFController', 'validate/' => 'PhabricatorLoginValidateController', 'mustverify/' => 'PhabricatorMustVerifyEmailController', ), '/logout/' => 'PhabricatorLogoutController', '/oauth/' => array( '(?P\w+)/' => array( 'login/' => 'PhabricatorOAuthLoginController', 'diagnose/' => 'PhabricatorOAuthDiagnosticsController', 'unlink/' => 'PhabricatorOAuthUnlinkController', ), ), '/ldap/' => array( 'login/' => 'PhabricatorLDAPLoginController', 'unlink/' => 'PhabricatorLDAPUnlinkController', ), '/oauthserver/' => array( 'auth/' => 'PhabricatorOAuthServerAuthController', 'test/' => 'PhabricatorOAuthServerTestController', 'token/' => 'PhabricatorOAuthServerTokenController', 'clientauthorization/' => array( '' => 'PhabricatorOAuthClientAuthorizationListController', 'delete/(?P[^/]+)/' => 'PhabricatorOAuthClientAuthorizationDeleteController', 'edit/(?P[^/]+)/' => 'PhabricatorOAuthClientAuthorizationEditController', ), 'client/' => array( '' => 'PhabricatorOAuthClientListController', 'create/' => 'PhabricatorOAuthClientEditController', 'delete/(?P[^/]+)/' => 'PhabricatorOAuthClientDeleteController', 'edit/(?P[^/]+)/' => 'PhabricatorOAuthClientEditController', 'view/(?P[^/]+)/' => 'PhabricatorOAuthClientViewController', ), ), '/xhprof/' => array( 'list/(?P[^/]+)/' => 'PhabricatorXHProfSampleListController', 'profile/(?P[^/]+)/' => 'PhabricatorXHProfProfileController', ), '/~/' => 'DarkConsoleController', '/search/' => array( '' => 'PhabricatorSearchController', '(?P[^/]+)/' => 'PhabricatorSearchController', 'attach/(?P[^/]+)/(?P\w+)/(?:(?P\w+)/)?' => 'PhabricatorSearchAttachController', 'select/(?P\w+)/' => 'PhabricatorSearchSelectController', 'index/(?P[^/]+)/' => 'PhabricatorSearchIndexController', ), '/status/' => 'PhabricatorStatusController', '/help/' => array( 'keyboardshortcut/' => 'PhabricatorHelpKeyboardShortcutController', ), '/chatlog/' => array( '' => 'PhabricatorChatLogChannelListController', 'channel/(?P[^/]+)/' => 'PhabricatorChatLogChannelLogController', ), '/notification/' => array( '(?:(?Pall|unread)/)?' => 'PhabricatorNotificationListController', 'panel/' => 'PhabricatorNotificationPanelController', 'individual/' => 'PhabricatorNotificationIndividualController', 'status/' => 'PhabricatorNotificationStatusController', 'clear/' => 'PhabricatorNotificationClearController', ), '/phortune/' => array( 'stripe/' => array( 'testpaymentform/' => 'PhortuneStripeTestPaymentFormController', ), ), ); } protected function getResourceURIMapRules() { return array( '/res/' => array( '(?Ppkg/)?'. '(?P[a-f0-9]{8})/'. '(?P.+\.(?:css|js|jpg|png|swf|gif))' => 'CelerityPhabricatorResourceController', ), ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); return $request; } public function handleException(Exception $ex) { $request = $this->getRequest(); // For Conduit requests, return a Conduit response. if ($request->isConduit()) { $response = new ConduitAPIResponse(); $response->setErrorCode(get_class($ex)); $response->setErrorInfo($ex->getMessage()); return id(new AphrontJSONResponse()) ->setContent($response->toDictionary()); } // For non-workflow requests, return a Ajax response. if ($request->isAjax() && !$request->isJavelinWorkflow()) { $response = new AphrontAjaxResponse(); $response->setError( array( 'code' => get_class($ex), 'info' => $ex->getMessage(), )); return $response; } $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $user = $request->getUser(); if (!$user) { // If we hit an exception very early, we won't have a user. $user = new PhabricatorUser(); } if ($ex instanceof PhabricatorPolicyException) { if (!$user->isLoggedIn()) { // If the user isn't logged in, just give them a login form. This is // probably a generally more useful response than a policy dialog that // they have to click through to get a login form. // // Possibly we should add a header here like "you need to login to see // the thing you are trying to look at". $login_controller = new PhabricatorLoginController($request); return $login_controller->processRequest(); } $content = '
'. phutil_escape_html($ex->getMessage()). '
'; $dialog = new AphrontDialogView(); $dialog ->setTitle( $is_serious ? 'Access Denied' : "You Shall Not Pass") ->setClass('aphront-access-dialog') ->setUser($user) ->appendChild($content); if ($this->getRequest()->isAjax()) { $dialog->addCancelButton('/', 'Close'); } else { $dialog->addCancelButton('/', $is_serious ? 'OK' : 'Away With Thee'); } $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } if ($ex instanceof AphrontUsageException) { $error = new AphrontErrorView(); $error->setTitle(phutil_escape_html($ex->getTitle())); $error->appendChild(phutil_escape_html($ex->getMessage())); $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->appendChild($error); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } // Always log the unhandled exception. phlog($ex); $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); if ($ex instanceof AphrontQuerySchemaException) { $message .= "\n\n". "NOTE: This usually indicates that the MySQL schema has not been ". "properly upgraded. Run 'bin/storage upgrade' to ensure your ". "schema is up to date."; } if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) { $trace = $this->renderStackTrace($ex->getTrace(), $user); } else { $trace = null; } $content = '
'. '
'.$message.'
'. $trace. '
'; $dialog = new AphrontDialogView(); $dialog ->setTitle('Unhandled Exception ("'.$class.'")') ->setClass('aphront-exception-dialog') ->setUser($user) ->appendChild($content); if ($this->getRequest()->isAjax()) { $dialog->addCancelButton('/', 'Close'); } $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } public function willSendResponse(AphrontResponse $response) { return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } public function buildRedirectController($uri) { return array( new PhabricatorRedirectController($this->getRequest()), array( 'uri' => $uri, )); } private function renderStackTrace($trace, PhabricatorUser $user) { $libraries = PhutilBootloader::getInstance()->getAllLibraries(); // TODO: Make this configurable? $path = 'https://secure.phabricator.com/diffusion/%s/browse/master/src/'; $callsigns = array( 'arcanist' => 'ARC', 'phutil' => 'PHU', 'phabricator' => 'P', ); $rows = array(); $depth = count($trace); foreach ($trace as $part) { $lib = null; $file = idx($part, 'file'); $relative = $file; foreach ($libraries as $library) { $root = phutil_get_library_root($library); if (Filesystem::isDescendant($file, $root)) { $lib = $library; $relative = Filesystem::readablePath($file, $root); break; } } $where = ''; if (isset($part['class'])) { $where .= $part['class'].'::'; } if (isset($part['function'])) { $where .= $part['function'].'()'; } if ($file) { if (isset($callsigns[$lib])) { $attrs = array('title' => $file); try { $attrs['href'] = $user->loadEditorLink( '/src/'.$relative, $part['line'], $callsigns[$lib]); } catch (Exception $ex) { // The database can be inaccessible. } if (empty($attrs['href'])) { $attrs['href'] = sprintf($path, $callsigns[$lib]). str_replace(DIRECTORY_SEPARATOR, '/', $relative). '$'.$part['line']; $attrs['target'] = '_blank'; } - $file_name = phutil_render_tag( + $file_name = phutil_tag( 'a', $attrs, - phutil_escape_html($relative)); + $relative); } else { - $file_name = phutil_render_tag( + $file_name = phutil_tag( 'span', array( 'title' => $file, ), - phutil_escape_html($relative)); + $relative); } $file_name = $file_name.' : '.(int)$part['line']; } else { $file_name = '(Internal)'; } $rows[] = array( $depth--, phutil_escape_html($lib), $file_name, phutil_escape_html($where), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Depth', 'Library', 'File', 'Where', )); $table->setColumnClasses( array( 'n', '', '', 'wide', )); return '
'. '
Stack Trace
'. $table->render(). '
'; } } diff --git a/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php b/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php index 20123516f..fae5ff2cb 100644 --- a/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php +++ b/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php @@ -1,151 +1,151 @@ getData()); if ($count) { return ' '. "Error Log ({$count})"; } return 'Error Log'; } public function getDescription() { return 'Shows errors and warnings.'; } public function generateData() { return DarkConsoleErrorLogPluginAPI::getErrors(); } public function render() { $data = $this->getData(); $rows = array(); $details = ''; foreach ($data as $index => $row) { $file = $row['file']; $line = $row['line']; - $tag = phutil_render_tag( + $tag = phutil_tag( 'a', array( 'onclick' => jsprintf('show_details(%d)', $index), ), - phutil_escape_html($row['str'].' at ['.basename($file).':'.$line.']')); + $row['str'].' at ['.basename($file).':'.$line.']'); $rows[] = array($tag); $details .= '
'. phutil_escape_html($row['details'])."\n". 'Stack trace:'."\n"; foreach ($row['trace'] as $key => $entry) { $line = ''; if (isset($entry['class'])) { $line .= $entry['class'].'::'; } $line .= idx($entry, 'function', ''); $href = null; if (isset($entry['file'])) { $line .= ' called at ['.$entry['file'].':'.$entry['line'].']'; try { $user = $this->getRequest()->getUser(); $href = $user->loadEditorLink($entry['file'], $entry['line'], ''); } catch (Exception $ex) { // The database can be inaccessible. } } - $details .= phutil_render_tag( + $details .= phutil_tag( 'a', array( 'href' => $href, ), - phutil_escape_html($line)); + $line); $details .= "\n"; } $details .= '
'; } $table = new AphrontTableView($rows); $table->setClassName('error-log'); $table->setHeaders(array('Error')); $table->setNoDataString('No errors.'); return '
'. '
'.$table->render().'
'. '
'.
       $details.'
'. '
'; } } /* $data = $this->getData(); if (!$data) { return
No errors.
; } $markup = ; $alt = false; foreach ($data as $error) { $row = ; $text = $error['error']; $text = preg_replace('/\(in .* on line \d+\)$/', '', trim($text)); $trace = $error['trace']; $trace = explode("\n", $trace); if (!$trace) { $trace = array('unknown@0@unknown'); } foreach ($trace as $idx => $traceline) { list($file, $line, $where) = array_merge( explode('@', $traceline), array('?', '?', '?')); if ($where == 'DarkConsole->addError' || $where == 'debug_rlog') { unset($trace[$idx]); } } $row->appendChild(); foreach ($trace as $traceline) { list($file, $line, $where) = array_merge( explode('@', $traceline), array('?', '?', '?')); $row->appendChild(); $row->appendChild(); $markup->appendChild($row); $row = ; } $alt = !$alt; } return

Errors

{$markup}
; */ diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php index 9f2a03dfd..1745e1f27 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php @@ -1,46 +1,46 @@ establishConnection('r'), 'SELECT DISTINCT channel FROM %T', $table->getTableName()); $rows = array(); foreach ($channels as $channel) { $name = $channel['channel']; $rows[] = array( - phutil_render_tag( + phutil_tag( 'a', array( 'href' => '/chatlog/channel/'.phutil_escape_uri($name).'/', ), - phutil_escape_html($name))); + $name)); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Channel', )); $table->setColumnClasses( array( 'pri wide', )); $panel = new AphrontPanelView(); $panel->appendChild($table); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Channel List', )); } } diff --git a/src/applications/conduit/controller/PhabricatorConduitListController.php b/src/applications/conduit/controller/PhabricatorConduitListController.php index 7c1689651..617ca4828 100644 --- a/src/applications/conduit/controller/PhabricatorConduitListController.php +++ b/src/applications/conduit/controller/PhabricatorConduitListController.php @@ -1,81 +1,81 @@ getMethodFilters(); $rows = array(); foreach ($method_groups as $group => $methods) { foreach ($methods as $info) { switch ($info['status']) { case ConduitAPIMethod::METHOD_STATUS_DEPRECATED: $status = 'Deprecated'; break; case ConduitAPIMethod::METHOD_STATUS_UNSTABLE: $status = 'Unstable'; break; default: $status = null; break; } $rows[] = array( $group, - phutil_render_tag( + phutil_tag( 'a', array( 'href' => '/conduit/method/'.$info['full_name'], ), - phutil_escape_html($info['full_name'])), + $info['full_name']), $info['description'], $status, ); $group = null; } } $table = new AphrontTableView($rows); $table->setHeaders(array( 'Group', 'Name', 'Description', 'Status', )); $table->setColumnClasses(array( 'pri', 'pri', 'wide', null, )); $panel = new AphrontPanelView(); $panel->setHeader('Conduit Methods'); $panel->appendChild($table); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $utils = new AphrontPanelView(); $utils->setHeader('Utilities'); $utils->appendChild( '
    '. '
  • Log - Conduit Method Calls
  • '. '
  • Token - Certificate Install
  • '. '
'); $utils->setWidth(AphrontPanelView::WIDTH_FULL); $this->setShowSideNav(false); return $this->buildStandardPageResponse( array( $panel, $utils, ), array( 'title' => 'Conduit Console', )); } } diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index e5bbc7552..e140ffc9e 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -1,97 +1,97 @@ getRequest(); $user = $request->getUser(); $rows = array(); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); ksort($options); foreach ($options as $option) { $key = $option->getKey(); if ($option->getMasked()) { $value = ''.pht('Masked').''; } else if ($option->getHidden()) { $value = ''.pht('Hidden').''; } else { $value = PhabricatorEnv::getEnvConfig($key); $value = PhabricatorConfigJSON::prettyPrintJSON($value); $value = phutil_escape_html($value); } $rows[] = array( - phutil_render_tag( + phutil_tag( 'a', array( 'href' => $this->getApplicationURI('edit/'.$key.'/'), ), - phutil_escape_html($key)), + $key), $value, ); } $table = id(new AphrontTableView($rows)) ->setDeviceReadyTable(true) ->setColumnClasses( array( '', 'wide', )) ->setHeaders( array( pht('Key'), pht('Value'), )); $title = pht('Current Settings'); $crumbs = $this ->buildApplicationCrumbs() ->addCrumb( id(new PhabricatorCrumbView()) ->setName($title)); $panel = new AphrontPanelView(); $panel->appendChild($table); $panel->setNoBackground(); $phabricator_root = dirname(phutil_get_library_root('phabricator')); $future = id(new ExecFuture('git log --format=%%H -n 1 --')) ->setCWD($phabricator_root); list($err, $stdout) = $future->resolve(); if (!$err) { $display_version = trim($stdout); } else { $display_version = pht('Unknown'); } $version_property_list = id(new PhabricatorPropertyListView()); $version_property_list->addProperty('Version', phutil_escape_html($display_version)); $version_path = $phabricator_root.'/conf/local/VERSION'; if (Filesystem::pathExists($version_path)) { $version_from_file = Filesystem::readFile($version_path); $version_property_list->addProperty('Local Version', phutil_escape_html($version_from_file)); } $nav = $this->buildSideNavView(); $nav->selectFilter('all/'); $nav->setCrumbs($crumbs); $nav->appendChild($version_property_list); $nav->appendChild($panel); return $this->buildApplicationPage( $nav, array( 'title' => $title, 'device' => true, ) ); } } diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index c85caed1e..645999f19 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -1,333 +1,333 @@ issue = $issue; return $this; } public function getIssue() { return $this->issue; } public function render() { $issue = $this->getIssue(); $description = phutil_render_tag( 'div', array( 'class' => 'setup-issue-instructions', ), nl2br(phutil_escape_html($issue->getMessage()))); $configs = $issue->getPHPConfig(); if ($configs) { $description .= $this->renderPHPConfig($configs); } $configs = $issue->getPhabricatorConfig(); if ($configs) { $description .= $this->renderPhabricatorConfig($configs); } $commands = $issue->getCommands(); if ($commands) { $run_these = pht("Run these %d command(s):", count($commands)); $description .= phutil_render_tag( 'div', array( 'class' => 'setup-issue-config', ), phutil_render_tag('p', array(), $run_these). phutil_render_tag('pre', array(), implode("\n", $commands))); } $extensions = $issue->getPHPExtensions(); if ($extensions) { $install_these = pht( "Install these %d PHP extension(s):", count($extensions)); $install_info = pht( "You can usually install a PHP extension using apt-get or ". "yum. Common package names are ". "php-extname or php5-extname. ". "Try commands like these:"); // TODO: We should do a better job of detecting how to install extensions // on the current system. $install_commands = array( "$ sudo apt-get install php5-extname # Debian / Ubuntu", "$ sudo yum install php-extname # Red Hat / Derivatives", ); $install_commands = implode("\n", $install_commands); $fallback_info = pht( "If those commands don't work, try Google. The process of installing ". "PHP extensions is not specific to Phabricator, and any instructions ". "you can find for installing them on your system should work. On Mac ". "OS X, you might want to try Homebrew."); $restart_info = pht( "After installing new PHP extensions, restart your webserver ". "for the changes to take effect."); $description .= phutil_render_tag( 'div', array( 'class' => 'setup-issue-config', ), phutil_render_tag('p', array(), $install_these). phutil_render_tag('pre', array(), implode("\n", $extensions)). phutil_render_tag('p', array(), $install_info). phutil_render_tag('pre', array(), $install_commands). phutil_render_tag('p', array(), $fallback_info). phutil_render_tag('p', array(), $restart_info)); } $next = phutil_render_tag( 'div', array( 'class' => 'setup-issue-next', ), pht('To continue, resolve this problem and reload the page.')); - $name = phutil_render_tag( + $name = phutil_tag( 'div', array( 'class' => 'setup-issue-name', ), - phutil_escape_html($issue->getName())); + $issue->getName()); return phutil_render_tag( 'div', array( 'class' => 'setup-issue', ), $name.$description.$next); } private function renderPhabricatorConfig(array $configs) { $issue = $this->getIssue(); $table_info = phutil_render_tag( 'p', array(), pht( "The current Phabricator configuration has these %d value(s):", count($configs))); $table = array(); foreach ($configs as $key) { $table[] = ''; $table[] = ''; $value = PhabricatorEnv::getUnrepairedEnvConfig($key); if ($value === null) { $value = 'null'; } else if ($value === false) { $value = 'false'; } else if ($value === true) { $value = 'true'; } else { $value = phutil_escape_html( PhabricatorConfigJSON::prettyPrintJSON($value)); } $table[] = ''; $table[] = ''; } $table = phutil_render_tag( 'table', array( ), implode("\n", $table)); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if ($this->getIssue()->getIsFatal()) { $update_info = phutil_render_tag( 'p', array(), pht( "To update these %d value(s), run these command(s) from the command ". "line:", count($configs))); $update = array(); foreach ($configs as $key) { $cmd = 'phabricator/ $ ./bin/config set '. phutil_escape_html($key).' '. 'value'; $update[] = $cmd; } $update = phutil_render_tag('pre', array(), implode("\n", $update)); } else { $update = array(); foreach ($configs as $config) { if (!idx($options, $config) || $options[$config]->getLocked()) { continue; } $link = phutil_render_tag( 'a', array( 'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(), ), pht('Edit %s', phutil_escape_html($config))); $update[] = '
  • '.$link.'
  • '; } if ($update) { $update = '
      '.implode("\n", $update).'
    '; $update_info = phutil_render_tag( 'p', array(), pht("You can update these %d value(s) here:", count($configs))); } else { $update = null; $update_info = null; } } return phutil_render_tag( 'div', array( 'class' => 'setup-issue-config', ), self::renderSingleView( array( $table_info, $table, $update_info, $update, ))); } private function renderPHPConfig(array $configs) { $table_info = phutil_render_tag( 'p', array(), pht( "The current PHP configuration has these %d value(s):", count($configs))); $table = array(); foreach ($configs as $key) { $table[] = ''; $table[] = ''; $value = ini_get($key); if ($value === null) { $value = 'null'; } else if ($value === false) { $value = 'false'; } else if ($value === true) { $value = 'true'; } else if ($value === '') { $value = '(empty string)'; } else { $value = phutil_escape_html($value); } $table[] = ''; $table[] = ''; } $table = phutil_render_tag( 'table', array( ), implode("\n", $table)); ob_start(); phpinfo(); $phpinfo = ob_get_clean(); $rex = '@Loaded Configuration File\s*@i'; $matches = null; $ini_loc = null; if (preg_match($rex, $phpinfo, $matches)) { $ini_loc = trim($matches[1]); } $rex = '@Additional \.ini files parsed\s*@i'; $more_loc = array(); if (preg_match($rex, $phpinfo, $matches)) { $more_loc = trim($matches[1]); if ($more_loc == '(none)') { $more_loc = array(); } else { $more_loc = preg_split('/\s*,\s*/', $more_loc); } } if (!$ini_loc) { $info = phutil_render_tag( 'p', array(), pht( "To update these %d value(s), edit your PHP configuration file.", count($configs))); } else { $info = phutil_render_tag( 'p', array(), pht( "To update these %d value(s), edit your PHP configuration file, ". "located here:", count($configs))); - $info .= phutil_render_tag( + $info .= phutil_tag( 'pre', array(), - phutil_escape_html($ini_loc)); + $ini_loc); } if ($more_loc) { $info .= phutil_render_tag( 'p', array(), pht( "PHP also loaded these configuration file(s):", count($more_loc))); - $info .= phutil_render_tag( + $info .= phutil_tag( 'pre', array(), - phutil_escape_html(implode("\n", $more_loc))); + implode("\n", $more_loc)); } $info .= phutil_render_tag( 'p', array(), pht( "You can find more information about PHP configuration values in the ". "%s.", phutil_render_tag( 'a', array( 'href' => 'http://php.net/manual/ini.list.php', ), pht('PHP Documentation')))); $info .= phutil_render_tag( 'p', array(), pht( "After editing the PHP configuration, restart your ". "webserver for the changes to take effect.")); return phutil_render_tag( 'div', array( 'class' => 'setup-issue-config', ), $table_info.$table.$info); } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index 1f8190254..47cc610c6 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -1,95 +1,95 @@ getRequest(); $user = $request->getUser(); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setURI($request->getRequestURI(), 'page'); $timers = id(new PhabricatorTimer())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); $timers = $pager->sliceResults($timers); $phids = mpull($timers, 'getAuthorPHID'); $handles = $this->loadViewerHandles($phids); $rows = array(); foreach ($timers as $timer) { $edit_button = null; $delete_button = null; if ($user->getIsAdmin() || ($user->getPHID() == $timer->getAuthorPHID())) { $edit_button = phutil_render_tag( 'a', array( 'class' => 'small button grey', 'href' => '/countdown/edit/'.$timer->getID().'/' ), 'Edit'); $delete_button = javelin_render_tag( 'a', array( 'class' => 'small button grey', 'href' => '/countdown/delete/'.$timer->getID().'/', 'sigil' => 'workflow' ), 'Delete'); } $rows[] = array( phutil_escape_html($timer->getID()), $handles[$timer->getAuthorPHID()]->renderLink(), - phutil_render_tag( + phutil_tag( 'a', array( 'href' => '/countdown/'.$timer->getID().'/', ), - phutil_escape_html($timer->getTitle())), + $timer->getTitle()), phabricator_datetime($timer->getDatepoint(), $user), $edit_button, $delete_button, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'ID', 'Author', 'Title', 'End Date', '', '' )); $table->setColumnClasses( array( null, null, 'wide pri', null, 'action', 'action', )); $panel = id(new AphrontPanelView()) ->appendChild($table) ->setHeader('Timers') ->setCreateButton('Create Timer', '/countdown/edit/') ->appendChild($pager); return $this->buildStandardPageResponse($panel, array( 'title' => 'Countdown', )); } } diff --git a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php index 57a3a5db2..732b3b6a4 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php @@ -1,109 +1,109 @@ events = $events; return $this; } public function setCombinedLog($is_combined) { $this->combinedLog = $is_combined; return $this; } public function render() { $rows = array(); if (!$this->user) { throw new Exception("Call setUser() before rendering!"); } foreach ($this->events as $event) { // Limit display log size. If a daemon gets stuck in an output loop this // page can be like >100MB if we don't truncate stuff. Try to do cheap // line-based truncation first, and fall back to expensive UTF-8 character // truncation if that doesn't get things short enough. $message = $event->getMessage(); $more_lines = null; $more_chars = null; $line_limit = 12; if (substr_count($message, "\n") > $line_limit) { $message = explode("\n", $message); $more_lines = count($message) - $line_limit; $message = array_slice($message, 0, $line_limit); $message = implode("\n", $message); } $char_limit = 8192; if (strlen($message) > $char_limit) { $message = phutil_utf8v($message); $more_chars = count($message) - $char_limit; $message = array_slice($message, 0, $char_limit); $message = implode('', $message); } $more = null; if ($more_chars) { $more = number_format($more_chars); $more = "\n<... {$more} more characters ...>"; } else if ($more_lines) { $more = number_format($more_lines); $more = "\n<... {$more} more lines ...>"; } $row = array( phutil_escape_html($event->getLogType()), phabricator_date($event->getEpoch(), $this->user), phabricator_time($event->getEpoch(), $this->user), str_replace("\n", '
    ', phutil_escape_html($message.$more)), ); if ($this->combinedLog) { array_unshift( $row, - phutil_render_tag( + phutil_tag( 'a', array( 'href' => '/daemon/log/'.$event->getLogID().'/', ), - phutil_escape_html('Daemon '.$event->getLogID()))); + 'Daemon '.$event->getLogID())); } $rows[] = $row; } $classes = array( '', '', 'right', 'wide wrap', ); $headers = array( 'Type', 'Date', 'Time', 'Message', ); if ($this->combinedLog) { array_unshift($classes, 'pri'); array_unshift($headers, 'Daemon'); } $log_table = new AphrontTableView($rows); $log_table->setHeaders($headers); $log_table->setColumnClasses($classes); return $log_table->render(); } } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index b212c2029..1ca4a9de7 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -1,156 +1,156 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $diff = id(new DifferentialDiff())->load($this->id); if (!$diff) { return new Aphront404Response(); } if ($diff->getRevisionID()) { $top_panel = new AphrontPanelView(); $top_panel->setWidth(AphrontPanelView::WIDTH_WIDE); - $link = phutil_render_tag( + $link = phutil_tag( 'a', array( 'href' => PhabricatorEnv::getURI('/D'.$diff->getRevisionID()), ), - phutil_escape_html('D'.$diff->getRevisionID())); + 'D'.$diff->getRevisionID()); $top_panel->appendChild( "

    ".pht('This diff belongs to revision %s', $link)."

    "); } else { $action_panel = new AphrontPanelView(); $action_panel->setHeader('Preview Diff'); $action_panel->setWidth(AphrontPanelView::WIDTH_WIDE); $action_panel->appendChild( '

    '.pht('Review the diff for '. 'correctness. When you are satisfied, either create a new '. 'revision or update an existing revision.')); // TODO: implmenent optgroup support in AphrontFormSelectControl? $select = array(); $select[] = ''; $select[] = ''; $select[] = ''; $revision_data = new DifferentialRevisionListData( DifferentialRevisionListData::QUERY_OPEN_OWNED, array($request->getUser()->getPHID())); $revisions = $revision_data->loadRevisions(); if ($revisions) { $select[] = ''; foreach ($revisions as $revision) { - $select[] = phutil_render_tag( + $select[] = phutil_tag( 'option', array( 'value' => $revision->getID(), ), - phutil_escape_html($revision->getTitle())); + $revision->getTitle()); } $select[] = ''; } $select = ''; $action_form = new AphrontFormView(); $action_form ->setUser($request->getUser()) ->setAction('/differential/revision/edit/') ->addHiddenInput('diffID', $diff->getID()) ->addHiddenInput('viaDiffView', 1) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Attach To')) ->setValue($select)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Continue'))); $action_panel->appendChild($action_form); $top_panel = $action_panel; } $props = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $diff->getID()); $props = mpull($props, 'getData', 'getName'); $aux_fields = DifferentialFieldSelector::newSelector() ->getFieldSpecifications(); foreach ($aux_fields as $key => $aux_field) { if (!$aux_field->shouldAppearOnDiffView()) { unset($aux_fields[$key]); } else { $aux_field->setUser($this->getRequest()->getUser()); } } $dict = array(); foreach ($aux_fields as $key => $aux_field) { $aux_field->setDiff($diff); $aux_field->setManualDiff($diff); $aux_field->setDiffProperties($props); $value = $aux_field->renderValueForDiffView(); if (strlen($value)) { $label = rtrim($aux_field->renderLabelForDiffView(), ':'); $dict[$label] = $value; } } $action_panel = new AphrontHeadsupView(); $action_panel->setProperties($dict); $action_panel->setHeader(pht('Diff Properties')); $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); $table_of_contents = id(new DifferentialDiffTableOfContentsView()) ->setChangesets($changesets) ->setVisibleChangesets($changesets) ->setUnitTestData(idx($props, 'arc:unit', array())); $refs = array(); foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } $details = id(new DifferentialChangesetListView()) ->setChangesets($changesets) ->setVisibleChangesets($changesets) ->setRenderingReferences($refs) ->setStandaloneURI('/differential/changeset/') ->setDiff($diff) ->setTitle(pht('Diff %d', $diff->getID())) ->setUser($request->getUser()); return $this->buildStandardPageResponse( id(new DifferentialPrimaryPaneView()) ->appendChild( array( $top_panel->render(), $action_panel->render(), $table_of_contents->render(), $details->render(), )), array( 'title' => pht('Diff View'), )); } } diff --git a/src/applications/differential/field/specification/DifferentialTitleFieldSpecification.php b/src/applications/differential/field/specification/DifferentialTitleFieldSpecification.php index 6a1c6ce20..07095e080 100644 --- a/src/applications/differential/field/specification/DifferentialTitleFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialTitleFieldSpecification.php @@ -1,98 +1,98 @@ title = $this->getRevision()->getTitle(); } public function setValueFromRequest(AphrontRequest $request) { $this->title = $request->getStr('title'); $this->error = null; return $this; } public function renderEditControl() { return id(new AphrontFormTextAreaControl()) ->setLabel('Title') ->setName('title') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setError($this->error) ->setValue($this->title); } public function shouldExtractMentions() { return true; } public function willWriteRevision(DifferentialRevisionEditor $editor) { $this->getRevision()->setTitle($this->title); } public function validateField() { if (!strlen($this->title)) { $this->error = 'Required'; throw new DifferentialFieldValidationException( "You must provide a title."); } } public function shouldAppearOnCommitMessage() { return true; } public function getCommitMessageKey() { return 'title'; } public function setValueFromParsedCommitMessage($value) { $this->title = $value; return $this; } public function shouldOverwriteWhenCommitMessageIsEdited() { return true; } public function renderLabelForCommitMessage() { return 'Title'; } public function renderValueForCommitMessage($is_edit) { return $this->title; } public function parseValueFromCommitMessage($value) { return preg_replace('/\s*\n\s*/', ' ', $value); } public function shouldAppearOnRevisionList() { return true; } public function renderHeaderForRevisionList() { return 'Revision'; } public function getColumnClassForRevisionList() { return 'wide pri'; } public function renderValueForRevisionList(DifferentialRevision $revision) { - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => '/D'.$revision->getID(), ), - phutil_escape_html($revision->getTitle())); + $revision->getTitle()); } } diff --git a/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php b/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php index e13117d2b..dfa474be6 100644 --- a/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php +++ b/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php @@ -1,131 +1,131 @@ anchorName = $anchor_name; return $this; } public function getAnchorName() { return $this->anchorName; } public function setBaseURI(PhutilURI $base_uri) { $this->baseURI = $base_uri; return $this; } public function getBaseURI() { return $this->baseURI; } public function setTitle($title) { $this->title = $title; return $this; } public function getTitle() { return $this->title; } public function build(array $changesets) { assert_instances_of($changesets, 'DifferentialChangeset'); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI($this->getBaseURI()); $nav->setFlexible(true); $anchor = $this->getAnchorName(); $tree = new PhutilFileTree(); foreach ($changesets as $changeset) { try { $tree->addPath($changeset->getFilename(), $changeset); } catch (Exception $ex) { // TODO: See T1702. When viewing the versus diff of diffs, we may // have files with the same filename. For example, if you have a setup // like this in SVN: // // a/ // README // b/ // README // // ...and you run "arc diff" once from a/, and again from b/, you'll // get two diffs with path README. However, in the versus diff view we // will compute their absolute repository paths and detect that they // aren't really the same file. This is correct, but causes us to // throw when inserting them. // // We should probably compute the smallest unique path for each file // and show these as "a/README" and "b/README" when diffed against // one another. However, we get this wrong in a lot of places (the // other TOC shows two "README" files, and we generate the same anchor // hash for both) so I'm just stopping the bleeding until we can get // a proper fix in place. } } require_celerity_resource('phabricator-filetree-view-css'); $filetree = array(); $path = $tree; while (($path = $path->getNextNode())) { $data = $path->getData(); $name = $path->getName(); $style = 'padding-left: '.(2 + (3 * $path->getDepth())).'px'; $href = null; if ($data) { $href = '#'.$data->getAnchorName(); $title = $name; $icon = 'phabricator-filetree-icon-file'; } else { $name .= '/'; $title = $path->getFullPath().'/'; $icon = 'phabricator-filetree-icon-dir'; } $icon = phutil_render_tag( 'span', array( 'class' => 'phabricator-filetree-icon '.$icon, ), ''); - $name_element = phutil_render_tag( + $name_element = phutil_tag( 'span', array( 'class' => 'phabricator-filetree-name', ), - phutil_escape_html($name)); + $name); $filetree[] = javelin_render_tag( $href ? 'a' : 'span', array( 'href' => $href, 'style' => $style, 'title' => $title, 'class' => 'phabricator-filetree-item', ), $icon.$name_element); } $tree->destroy(); $filetree = '

    '. implode("\n", $filetree). '
    '; $nav->addLabel(pht('Changed Files')); $nav->addCustomBlock($filetree); $nav->setActive(true); $nav->selectFilter(null); return $nav; } } diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index ff00f973e..8e7e9f926 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -1,323 +1,323 @@ 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'); $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_render_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 = ' class="alt"'; } else { $class = null; } if ($diff) { $lint = self::renderDiffLintStar($row['obj']); $unit = self::renderDiffUnitStar($row['obj']); $lint_message = self::getDiffLintMessage($diff); $unit_message = self::getDiffUnitMessage($diff); $lint_title = ' title="'.phutil_escape_html($lint_message).'"'; $unit_title = ' title="'.phutil_escape_html($unit_message).'"'; $base = $this->renderBaseRevision($diff); } else { $lint = null; $unit = null; $lint_title = null; $unit_title = null; $base = null; } if ($last_base !== null && $base !== $last_base) { // TODO: Render some kind of notice about rebases. } $last_base = $base; - $id_link = phutil_render_tag( + $id_link = phutil_tag( 'a', array('href' => '/differential/diff/'.$id.'/'), - phutil_escape_html($id)); + $id); $rows[] = ''. ''. ''. ''. ''. ''. ''. ''. ''. ''. ''; } 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', ); $select = ''; return id(new PhabricatorHeaderView()) ->setHeader(pht('Revision Update History')) ->render() . '
    '. '
    '. '
    {$text}{$file}:{$line}{$where}()
    '.phutil_escape_html($key).''.$value.'
    '.phutil_escape_html($key).''.$value.'
    (.*?)(.*?)'.phutil_escape_html($name).''.$id_link.''.phutil_escape_html($base).''.phutil_escape_html($desc).''.$age.''.$lint.''.$unit.''.$old.''.$new.'
    '. ''. ''. ''. ''. ''. ''. ''. ''. ''. implode("\n", $rows). ''. ''. ''. '
    '.pht('Diff').''.pht('ID').''.pht('Base').''.pht('Description').''.pht('Created').''.pht('Lint').''.pht('Unit').'
    '. ''. ''. '
    '. ''. ''; } 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 ''. "\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/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php index edab2227e..c0835332f 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php @@ -1,948 +1,948 @@ getRequest(); $drequest = $this->getDiffusionRequest(); $before = $request->getStr('before'); if ($before) { return $this->buildBeforeResponse($before); } $path = $drequest->getPath(); $selected = $request->getStr('view'); $preferences = $request->getUser()->loadPreferences(); if (!$selected) { $selected = $preferences->getPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW, 'highlighted'); } else if ($request->isFormPost() && $selected != 'raw') { $preferences->setPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW, $selected); $preferences->save(); return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('view', $selected)); } $needs_blame = ($selected == 'blame' || $selected == 'plainblame'); $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( $this->diffusionRequest); $file_query->setViewer($request->getUser()); $file_query->setNeedsBlame($needs_blame); $file_query->loadFileContent(); $data = $file_query->getRawData(); if ($selected === 'raw') { return $this->buildRawResponse($path, $data); } $this->loadLintMessages(); // Build the content of the file. $corpus = $this->buildCorpus( $selected, $file_query, $needs_blame, $drequest, $path, $data); require_celerity_resource('diffusion-source-css'); if ($this->corpusType == 'text') { $view_select_panel = $this->renderViewSelectPanel($selected); } else { $view_select_panel = null; } // Render the page. $content = array(); $follow = $request->getStr('follow'); if ($follow) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_WARNING); $notice->setTitle('Unable to Continue'); switch ($follow) { case 'first': $notice->appendChild( "Unable to continue tracing the history of this file because ". "this commit is the first commit in the repository."); break; case 'created': $notice->appendChild( "Unable to continue tracing the history of this file because ". "this commit created the file."); break; } $content[] = $notice; } $renamed = $request->getStr('renamed'); if ($renamed) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('File Renamed'); $notice->appendChild( "File history passes through a rename from '". phutil_escape_html($drequest->getPath())."' to '". phutil_escape_html($renamed)."'."); $content[] = $notice; } $content[] = $view_select_panel; $content[] = $corpus; $content[] = $this->buildOpenRevisions(); $nav = $this->buildSideNav('browse', true); $nav->appendChild($content); $crumbs = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); $nav->setCrumbs($crumbs); $basename = basename($this->getDiffusionRequest()->getPath()); return $this->buildApplicationPage( $nav, array( 'title' => $basename, )); } private function loadLintMessages() { $drequest = $this->getDiffusionRequest(); $branch = $drequest->loadBranch(); if (!$branch || !$branch->getLintCommit()) { return; } $this->lintCommit = $branch->getLintCommit(); $conn = id(new PhabricatorRepository())->establishConnection('r'); $where = ''; if ($drequest->getLint()) { $where = qsprintf( $conn, 'AND code = %s', $drequest->getLint()); } $this->lintMessages = queryfx_all( $conn, 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', PhabricatorRepository::TABLE_LINTMESSAGE, $branch->getID(), $where, '/'.$drequest->getPath()); } private function buildCorpus($selected, DiffusionFileContentQuery $file_query, $needs_blame, DiffusionRequest $drequest, $path, $data) { if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file = $this->loadFileForData($path, $data); $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { $this->corpusType = 'image'; return $this->buildImageCorpus($file_uri); } else { $this->corpusType = 'binary'; return $this->buildBinaryCorpus($file_uri, $data); } } switch ($selected) { case 'plain': $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; - $corpus = phutil_render_tag( + $corpus = phutil_tag( 'textarea', array( 'style' => $style, ), - phutil_escape_html($file_query->getRawData())); + $file_query->getRawData()); break; case 'plainblame': $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); $rows = array(); foreach ($text_list as $k => $line) { $rev = $rev_list[$k]; if (isset($blame_dict[$rev]['handle'])) { $author = $blame_dict[$rev]['handle']->getName(); } else { $author = $blame_dict[$rev]['author']; } $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line); } - $corpus = phutil_render_tag( + $corpus = phutil_tag( 'textarea', array( 'style' => $style, ), - phutil_escape_html(implode("\n", $rows))); + implode("\n", $rows)); break; case 'highlighted': case 'blame': default: require_celerity_resource('syntax-highlighting-css'); list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); $text_list = implode("\n", $text_list); $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $text_list); $text_list = explode("\n", $text_list); $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $file_query, $selected); $id = celerity_generate_unique_node_id(); $projects = $drequest->loadArcanistProjects(); $langs = array(); foreach ($projects as $project) { $ls = $project->getSymbolIndexLanguages(); if (!$ls) { continue; } $dep_projects = $project->getSymbolIndexProjects(); $dep_projects[] = $project->getPHID(); foreach ($ls as $lang) { if (!isset($langs[$lang])) { $langs[$lang] = array(); } $langs[$lang] += $dep_projects + array($project); } } $lang = last(explode('.', $drequest->getPath())); $prefs = $this->getRequest()->getUser()->loadPreferences(); $pref_symbols = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS); if (isset($langs[$lang]) && $pref_symbols != 'disabled') { Javelin::initBehavior( 'repository-crossreference', array( 'container' => $id, 'lang' => $lang, 'projects' => $langs[$lang], )); } $corpus_table = javelin_render_tag( 'table', array( 'class' => "diffusion-source remarkup-code PhabricatorMonospaced", 'sigil' => 'diffusion-source', ), implode("\n", $rows)); $corpus = phutil_render_tag( 'div', array( 'style' => 'padding: 0 2em;', 'id' => $id, ), $corpus_table); break; } return $corpus; } private function renderViewSelectPanel($selected) { $toggle_blame = array( 'highlighted' => 'blame', 'blame' => 'highlighted', 'plain' => 'plainblame', 'plainblame' => 'plain', 'raw' => 'raw', // not a real case. ); $toggle_highlight = array( 'highlighted' => 'plain', 'blame' => 'plainblame', 'plain' => 'highlighted', 'plainblame' => 'blame', 'raw' => 'raw', // not a real case. ); $user = $this->getRequest()->getUser(); $base_uri = $this->getRequest()->getRequestURI(); $blame_on = ($selected == 'blame' || $selected == 'plainblame'); if ($blame_on) { $blame_text = pht('Disable Blame'); } else { $blame_text = pht('Enable Blame'); } $blame_button = $this->createViewAction( $blame_text, $base_uri->alter('view', $toggle_blame[$selected]), $user); $highlight_on = ($selected == 'blame' || $selected == 'highlighted'); if ($highlight_on) { $highlight_text = pht('Disable Highlighting'); } else { $highlight_text = pht('Enable Highlighting'); } $highlight_button = $this->createViewAction( $highlight_text, $base_uri->alter('view', $toggle_highlight[$selected]), $user); $href = null; if ($this->getRequest()->getStr('lint') !== null) { $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); $href = $base_uri->alter('lint', null); } else if ($this->lintCommit === null) { $lint_text = pht('Lint not Available'); } else { $lint_text = pht( 'Show %d Lint Message(s)', count($this->lintMessages)); $href = $this->getDiffusionRequest()->generateURI(array( 'action' => 'browse', 'commit' => $this->lintCommit, ))->alter('lint', ''); } $lint_button = $this->createViewAction( $lint_text, $href, $user); if (!$href) { $lint_button->setDisabled(true); } $raw_button = $this->createViewAction( pht('View Raw File'), $base_uri->alter('view', 'raw'), $user, 'file'); $edit_button = $this->createEditAction(); return id(new PhabricatorActionListView()) ->setUser($user) ->addAction($blame_button) ->addAction($highlight_button) ->addAction($lint_button) ->addAction($raw_button) ->addAction($edit_button); } private function createViewAction( $localized_text, $href, $user, $icon = null) { return id(new PhabricatorActionView()) ->setName($localized_text) ->setIcon($icon) ->setUser($user) ->setRenderAsForm(true) ->setHref($href); } private function createEditAction() { $request = $this->getRequest(); $user = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $path = $drequest->getPath(); $line = nonempty((int)$drequest->getLine(), 1); $callsign = $repository->getCallsign(); $editor_link = $user->loadEditorLink($path, $line, $callsign); $action = id(new PhabricatorActionView()) ->setName(pht('Open in Editor')) ->setIcon('edit'); $action->setHref($editor_link); $action->setDisabled(!$editor_link); return $action; } private function buildDisplayRows( array $text_list, array $rev_list, array $blame_dict, $needs_blame, DiffusionRequest $drequest, DiffusionFileContentQuery $file_query, $selected) { if ($blame_dict) { $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); $epoch_min = min($epoch_list); $epoch_max = max($epoch_list); $epoch_range = ($epoch_max - $epoch_min) + 1; } $line_arr = array(); $line_str = $drequest->getLine(); $ranges = explode(',', $line_str); foreach ($ranges as $range) { if (strpos($range, '-') !== false) { list($min, $max) = explode('-', $range, 2); $line_arr[] = array( 'min' => min($min, $max), 'max' => max($min, $max), ); } else if (strlen($range)) { $line_arr[] = array( 'min' => $range, 'max' => $range, ); } } $display = array(); $line_number = 1; $last_rev = null; $color = null; foreach ($text_list as $k => $line) { $display_line = array( 'color' => null, 'epoch' => null, 'commit' => null, 'author' => null, 'target' => null, 'highlighted' => null, 'line' => $line_number, 'data' => $line, ); if ($needs_blame) { // If the line's rev is same as the line above, show empty content // with same color; otherwise generate blame info. The newer a change // is, the more saturated the color. $rev = idx($rev_list, $k, $last_rev); if ($last_rev == $rev) { $display_line['color'] = $color; } else { $blame = $blame_dict[$rev]; if (!isset($blame['epoch'])) { $color = '#ffd'; // Render as warning. } else { $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; $color_value = 0xF6 * (1.0 - $color_ratio); $color = sprintf( '#%02x%02x%02x', $color_value, 0xF6, $color_value); } $display_line['epoch'] = idx($blame, 'epoch'); $display_line['color'] = $color; $display_line['commit'] = $rev; if (isset($blame['handle'])) { $author_link = $blame['handle']->renderLink(); } else { - $author_link = phutil_render_tag( + $author_link = phutil_tag( 'span', array( ), - phutil_escape_html($blame['author'])); + $blame['author']); } $display_line['author'] = $author_link; $last_rev = $rev; } } if ($line_arr) { if ($line_number == $line_arr[0]['min']) { $display_line['target'] = true; } foreach ($line_arr as $range) { if ($line_number >= $range['min'] && $line_number <= $range['max']) { $display_line['highlighted'] = true; } } } $display[] = $display_line; ++$line_number; } $commits = array_filter(ipull($display, 'commit')); if ($commits) { $commits = id(new PhabricatorAuditCommitQuery()) ->withIdentifiers($drequest->getRepository()->getID(), $commits) ->needCommitData(true) ->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } $revision_ids = id(new DifferentialRevision()) ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); $revisions = array(); if ($revision_ids) { $revisions = id(new DifferentialRevision())->loadAllWhere( 'id IN (%Ld)', $revision_ids); } $request = $this->getRequest(); $user = $request->getUser(); Javelin::initBehavior('phabricator-oncopy', array()); $engine = null; $inlines = array(); if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { $engine = new PhabricatorMarkupEngine(); $engine->setViewer($user); foreach ($this->lintMessages as $message) { $inline = id(new PhabricatorAuditInlineComment()) ->setID($message['id']) ->setSyntheticAuthor( ArcanistLintSeverity::getStringForSeverity($message['severity']). ' '.$message['code'].' ('.$message['name'].')') ->setLineNumber($message['line']) ->setContent($message['description']); $inlines[$message['line']][] = $inline; $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); require_celerity_resource('differential-changeset-view-css'); } $rows = $this->renderInlines( idx($inlines, 0, array()), $needs_blame, $engine); foreach ($display as $line) { $line_href = $drequest->generateURI( array( 'action' => 'browse', 'line' => $line['line'], 'stable' => true, )); $blame = array(); if ($line['color']) { $color = $line['color']; $before_link = null; $commit_link = null; $revision_link = null; if (idx($line, 'commit')) { $commit = $line['commit']; $summary = 'Unknown'; if (idx($commits, $commit)) { $summary = $commits[$commit]->getCommitData()->getSummary(); } $tooltip = phabricator_date( $line['epoch'], $user)." \xC2\xB7 ".$summary; Javelin::initBehavior('phabricator-tooltips', array()); require_celerity_resource('aphront-tooltip-css'); $commit_link = javelin_render_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'commit', 'commit' => $line['commit'], )), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $tooltip, 'align' => 'E', 'size' => 600, ), ), phutil_escape_html(phutil_utf8_shorten($line['commit'], 9, ''))); $revision_id = null; if (idx($commits, $commit)) { $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); } if ($revision_id) { $revision = idx($revisions, $revision_id); if (!$revision) { $tooltip = '(Invalid revision)'; } else { $tooltip = phabricator_date($revision->getDateModified(), $user). " \xC2\xB7 ". $revision->getTitle(); } $revision_link = javelin_render_tag( 'a', array( 'href' => '/D'.$revision_id, 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $tooltip, 'align' => 'E', 'size' => 600, ), ), 'D'.$revision_id); } $uri = $line_href->alter('before', $commit); $before_link = javelin_render_tag( 'a', array( 'href' => $uri->setQueryParam('view', 'blame'), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => 'Skip Past This Commit', 'align' => 'E', 'size' => 300, ), ), "\xC2\xAB"); } $blame[] = phutil_render_tag( 'th', array( 'class' => 'diffusion-blame-link', 'style' => 'background: '.$color, ), $before_link); $blame[] = phutil_render_tag( 'th', array( 'class' => 'diffusion-rev-link', 'style' => 'background: '.$color, ), $commit_link); $blame[] = phutil_render_tag( 'th', array( 'class' => 'diffusion-rev-link', 'style' => 'background: '.$color, ), $revision_link); $blame[] = phutil_render_tag( 'th', array( 'class' => 'diffusion-author-link', 'style' => 'background: '.$color, ), idx($line, 'author')); } - $line_link = phutil_render_tag( + $line_link = phutil_tag( 'a', array( 'href' => $line_href, ), - phutil_escape_html($line['line'])); + $line['line']); $blame[] = javelin_render_tag( 'th', array( 'class' => 'diffusion-line-link', 'sigil' => 'diffusion-line-link', 'style' => isset($color) ? 'background: '.$color : null, ), $line_link); Javelin::initBehavior('diffusion-line-linker'); $blame = implode('', $blame); if ($line['target']) { Javelin::initBehavior( 'diffusion-jump-to', array( 'target' => 'scroll_target', )); $anchor_text = ''; } else { $anchor_text = null; } $line_text = phutil_render_tag( 'td', array( ), $anchor_text. "\xE2\x80\x8B". // NOTE: See phabricator-oncopy behavior. $line['data']); $rows[] = phutil_render_tag( 'tr', array( 'class' => ($line['highlighted'] ? 'highlighted' : null), ), $blame. $line_text); $rows = array_merge($rows, $this->renderInlines( idx($inlines, $line['line'], array()), $needs_blame, $engine)); } return $rows; } private function renderInlines(array $inlines, $needs_blame, $engine) { $rows = array(); foreach ($inlines as $inline) { $inline_view = id(new DifferentialInlineCommentView()) ->setMarkupEngine($engine) ->setInlineComment($inline) ->render(); $rows[] = ''. str_repeat('', ($needs_blame ? 5 : 1)). ''.$inline_view.''. ''; } return $rows; } private function loadFileForData($path, $data) { return PhabricatorFile::buildFromFileDataOrHash( $data, array( 'name' => basename($path), )); } private function buildRawResponse($path, $data) { $file = $this->loadFileForData($path, $data); return id(new AphrontRedirectResponse())->setURI($file->getBestURI()); } private function buildImageCorpus($file_uri) { $properties = new PhabricatorPropertyListView(); $properties->addProperty( pht('Image'), phutil_tag( 'img', array( 'src' => $file_uri, ))); $actions = id(new PhabricatorActionListView()) ->setUser($this->getRequest()->getUser()) ->addAction($this->createEditAction()); return array($actions, $properties); } private function buildBinaryCorpus($file_uri, $data) { $properties = new PhabricatorPropertyListView(); $size = strlen($data); $properties->addTextContent( pht('This is a binary file. It is %2$s byte(s) in length.', $size, PhutilTranslator::getInstance()->formatNumber($size)) ); $actions = id(new PhabricatorActionListView()) ->setUser($this->getRequest()->getUser()) ->addAction($this->createEditAction()) ->addAction(id(new PhabricatorActionView()) ->setName(pht('Download Binary File...')) ->setIcon('download') ->setHref($file_uri)); return array($actions, $properties); } private function buildBeforeResponse($before) { $request = $this->getRequest(); $drequest = $this->getDiffusionRequest(); // NOTE: We need to get the grandparent so we can capture filename changes // in the parent. $parent = $this->loadParentRevisionOf($before); $old_filename = null; $was_created = false; if ($parent) { $grandparent = $this->loadParentRevisionOf( $parent->getCommitIdentifier()); if ($grandparent) { $rename_query = new DiffusionRenameHistoryQuery(); $rename_query->setRequest($drequest); $rename_query->setOldCommit($grandparent->getCommitIdentifier()); $old_filename = $rename_query->loadOldFilename(); $was_created = $rename_query->getWasCreated(); } } $follow = null; if ($was_created) { // If the file was created in history, that means older commits won't // have it. Since we know it existed at 'before', it must have been // created then; jump there. $target_commit = $before; $follow = 'created'; } else if ($parent) { // If we found a parent, jump to it. This is the normal case. $target_commit = $parent->getCommitIdentifier(); } else { // If there's no parent, this was probably created in the initial commit? // And the "was_created" check will fail because we can't identify the // grandparent. Keep the user at 'before'. $target_commit = $before; $follow = 'first'; } $path = $drequest->getPath(); $renamed = null; if ($old_filename !== null && $old_filename !== '/'.$path) { $renamed = $path; $path = $old_filename; } $line = null; // If there's a follow error, drop the line so the user sees the message. if (!$follow) { $line = $this->getBeforeLineNumber($target_commit); } $before_uri = $drequest->generateURI( array( 'action' => 'browse', 'commit' => $target_commit, 'line' => $line, 'path' => $path, )); $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); $before_uri = $before_uri->alter('before', null); $before_uri = $before_uri->alter('renamed', $renamed); $before_uri = $before_uri->alter('follow', $follow); return id(new AphrontRedirectResponse())->setURI($before_uri); } private function getBeforeLineNumber($target_commit) { $drequest = $this->getDiffusionRequest(); $line = $drequest->getLine(); if (!$line) { return null; } $diff_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest); $diff_query->setAgainstCommit($target_commit); try { $raw_diff = $diff_query->loadRawDiff(); $old_line = 0; $new_line = 0; foreach (explode("\n", $raw_diff) as $text) { if ($text[0] == '-' || $text[0] == ' ') { $old_line++; } if ($text[0] == '+' || $text[0] == ' ') { $new_line++; } if ($new_line == $line) { return $old_line; } } // We didn't find the target line. return $line; } catch (Exception $ex) { return $line; } } private function loadParentRevisionOf($commit) { $drequest = $this->getDiffusionRequest(); $before_req = DiffusionRequest::newFromDictionary( array( 'repository' => $drequest->getRepository(), 'commit' => $commit, )); $query = DiffusionCommitParentsQuery::newFromDiffusionRequest($before_req); $parents = $query->loadParents(); return head($parents); } } diff --git a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php index c456d5e9a..4b306f5b1 100644 --- a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php @@ -1,32 +1,32 @@ diffusionRequest = DiffusionRequest::newFromDictionary($data); } public function processRequest() { $request = $this->getDiffusionRequest(); $branch_query = DiffusionContainsQuery::newFromDiffusionRequest($request); $branches = $branch_query->loadContainingBranches(); $branch_links = array(); foreach ($branches as $branch => $commit) { - $branch_links[] = phutil_render_tag( + $branch_links[] = phutil_tag( 'a', array( 'href' => $request->generateURI( array( 'action' => 'browse', 'branch' => $branch, )), ), - phutil_escape_html($branch)); + $branch); } return id(new AphrontAjaxResponse()) ->setContent($branch_links ? implode(', ', $branch_links) : 'None'); } } diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 72ba66284..8afcc4b0e 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -1,923 +1,923 @@ diffusionRequest = $drequest; } public function processRequest() { $drequest = $this->getDiffusionRequest(); $request = $this->getRequest(); $user = $request->getUser(); if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); } $callsign = $drequest->getRepository()->getCallsign(); $content = array(); $repository = $drequest->getRepository(); $commit = $drequest->loadCommit(); if (!$commit) { $query = DiffusionExistsQuery::newFromDiffusionRequest($drequest); $exists = $query->loadExistentialData(); if (!$exists) { return new Aphront404Response(); } return $this->buildStandardPageResponse( id(new AphrontErrorView()) ->setTitle('Error displaying commit.') ->appendChild('Failed to load the commit because the commit has not '. 'been parsed yet.'), array('title' => 'Commit Still Parsing') ); } $commit_data = $drequest->loadCommitData(); $commit->attachCommitData($commit_data); $top_anchor = id(new PhabricatorAnchorView()) ->setAnchorName('top') ->setNavigationMarker(true); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); $changesets = null; if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); $error_panel = new AphrontErrorView(); $error_panel->setTitle('Commit Not Tracked'); $error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING); $error_panel->appendChild( "This Diffusion repository is configured to track only one ". "subdirectory of the entire Subversion repository, and this commit ". "didn't affect the tracked subdirectory ('". phutil_escape_html($subpath)."'), so no information is available."); $content[] = $error_panel; $content[] = $top_anchor; } else { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); require_celerity_resource('diffusion-commit-view-css'); require_celerity_resource('phabricator-remarkup-css'); $parent_query = DiffusionCommitParentsQuery::newFromDiffusionRequest( $drequest); $headsup_view = id(new PhabricatorHeaderView()) ->setHeader('Commit Detail'); $headsup_actions = $this->renderHeadsupActionList($commit, $repository); $commit_properties = $this->loadCommitProperties( $commit, $commit_data, $parent_query->loadParents() ); $property_list = id(new PhabricatorPropertyListView()) ->setHasKeyboardShortcuts(true); foreach ($commit_properties as $key => $value) { $property_list->addProperty($key, $value); } $property_list->addTextContent( '
    '. $engine->markupText($commit_data->getCommitMessage()). '
    ' ); $content[] = $top_anchor; $content[] = $headsup_view; $content[] = $headsup_actions; $content[] = $property_list; } $query = new PhabricatorAuditQuery(); $query->withCommitPHIDs(array($commit->getPHID())); $audit_requests = $query->execute(); $this->auditAuthorityPHIDs = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $content[] = $this->buildAuditTable($commit, $audit_requests); $content[] = $this->buildComments($commit); $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( $drequest); $changes = $change_query->loadChanges(); $content[] = $this->buildMergesTable($commit); $owners_paths = array(); if ($this->highlightedAudits) { $packages = id(new PhabricatorOwnersPackage())->loadAllWhere( 'phid IN (%Ls)', mpull($this->highlightedAudits, 'getAuditorPHID')); if ($packages) { $owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere( 'repositoryPHID = %s AND packageID IN (%Ld)', $repository->getPHID(), mpull($packages, 'getID')); } } $change_table = new DiffusionCommitChangeTableView(); $change_table->setDiffusionRequest($drequest); $change_table->setPathChanges($changes); $change_table->setOwnersPaths($owners_paths); $count = count($changes); $bad_commit = null; if ($count == 0) { $bad_commit = queryfx_one( id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, 'r'.$callsign.$commit->getCommitIdentifier()); } $pane_id = null; if ($bad_commit) { $error_panel = new AphrontErrorView(); $error_panel->setTitle('Bad Commit'); $error_panel->appendChild( phutil_escape_html($bad_commit['description'])); $content[] = $error_panel; } else if ($is_foreign) { // Don't render anything else. } else if (!count($changes)) { $no_changes = new AphrontErrorView(); $no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING); $no_changes->setTitle('Not Yet Parsed'); // TODO: This can also happen with weird SVN changes that don't do // anything (or only alter properties?), although the real no-changes case // is extremely rare and might be impossible to produce organically. We // should probably write some kind of "Nothing Happened!" change into the // DB once we parse these changes so we can distinguish between // "not parsed yet" and "no changes". $no_changes->appendChild( "This commit hasn't been fully parsed yet (or doesn't affect any ". "paths)."); $content[] = $no_changes; } else { $change_panel = new AphrontPanelView(); $change_panel->setHeader("Changes (".number_format($count).")"); $change_panel->setID('toc'); if ($count > self::CHANGES_LIMIT) { - $show_all_button = phutil_render_tag( + $show_all_button = phutil_tag( 'a', array( 'class' => 'button green', 'href' => '?show_all=true', ), - phutil_escape_html('Show All Changes')); + 'Show All Changes'); $warning_view = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_WARNING) ->setTitle('Very Large Commit') ->appendChild( "

    This commit is very large. Load each file individually.

    "); $change_panel->appendChild($warning_view); $change_panel->addButton($show_all_button); } $change_panel->appendChild($change_table); $change_panel->setNoBackground(); $content[] = $change_panel; $changesets = DiffusionPathChange::convertToDifferentialChangesets( $changes); $vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $vcs_supports_directory_changes = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $vcs_supports_directory_changes = false; break; default: throw new Exception("Unknown VCS."); } $references = array(); foreach ($changesets as $key => $changeset) { $file_type = $changeset->getFileType(); if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { if (!$vcs_supports_directory_changes) { unset($changesets[$key]); continue; } } $references[$key] = $drequest->generateURI( array( 'action' => 'rendering-ref', 'path' => $changeset->getFilename(), )); } // TODO: Some parts of the views still rely on properties of the // DifferentialChangeset. Make the objects ephemeral to make sure we don't // accidentally save them, and then set their ID to the appropriate ID for // this application (the path IDs). $path_ids = array_flip(mpull($changes, 'getPath')); foreach ($changesets as $changeset) { $changeset->makeEphemeral(); $changeset->setID($path_ids[$changeset->getFilename()]); } if ($count <= self::CHANGES_LIMIT) { $visible_changesets = $changesets; } else { $visible_changesets = array(); $inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere( 'commitPHID = %s AND (auditCommentID IS NOT NULL OR authorPHID = %s)', $commit->getPHID(), $user->getPHID()); $path_ids = mpull($inlines, null, 'getPathID'); foreach ($changesets as $key => $changeset) { if (array_key_exists($changeset->getID(), $path_ids)) { $visible_changesets[$key] = $changeset; } } } $change_list_title = DiffusionView::nameCommit( $repository, $commit->getCommitIdentifier() ); $change_list = new DifferentialChangesetListView(); $change_list->setTitle($change_list_title); $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/'); $change_list->setRepository($repository); $change_list->setUser($user); // pick the first branch for "Browse in Diffusion" View Option $branches = $commit_data->getCommitDetail('seenOnBranches', array()); $first_branch = reset($branches); $change_list->setBranch($first_branch); $change_list->setStandaloneURI( '/diffusion/'.$callsign.'/diff/'); $change_list->setRawFileURIs( // TODO: Implement this, somewhat tricky if there's an octopus merge // or whatever? null, '/diffusion/'.$callsign.'/diff/?view=r'); $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); $change_references = array(); foreach ($changesets as $key => $changeset) { $change_references[$changeset->getID()] = $references[$key]; } $change_table->setRenderingReferences($change_references); // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); $pane_id = celerity_generate_unique_node_id(); $add_comment_view = $this->renderAddCommentPanel($commit, $audit_requests, $pane_id); $main_pane = phutil_render_tag( 'div', array( 'id' => $pane_id ), $change_list->render(). id(new PhabricatorAnchorView()) ->setAnchorName('comment') ->setNavigationMarker(true) ->render(). $add_comment_view); $content[] = $main_pane; } $commit_id = 'r'.$callsign.$commit->getCommitIdentifier(); $short_name = DiffusionView::nameCommit( $repository, $commit->getCommitIdentifier() ); $crumbs = $this->buildCrumbs(array( 'commit' => true, )); if ($changesets) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setAnchorName('top') ->setTitle($short_name) ->setBaseURI(new PhutilURI('/'.$commit_id)) ->build($changesets) ->setCrumbs($crumbs) ->appendChild($content); $content = $nav; } else { $content = array($crumbs, $content); } return $this->buildApplicationPage( $content, array( 'title' => $commit_id ) ); } private function loadCommitProperties( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $parents) { assert_instances_of($parents, 'PhabricatorRepositoryCommit'); $user = $this->getRequest()->getUser(); $commit_phid = $commit->getPHID(); $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) ->withEdgeTypes(array( PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK, PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT )) ->execute(); $task_phids = array_keys( $edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK] ); $proj_phids = array_keys( $edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT] ); $phids = array_merge($task_phids, $proj_phids); if ($data->getCommitDetail('authorPHID')) { $phids[] = $data->getCommitDetail('authorPHID'); } if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } if ($data->getCommitDetail('committerPHID')) { $phids[] = $data->getCommitDetail('committerPHID'); } if ($data->getCommitDetail('differential.revisionPHID')) { $phids[] = $data->getCommitDetail('differential.revisionPHID'); } if ($parents) { foreach ($parents as $parent) { $phids[] = $parent->getPHID(); } } $handles = array(); if ($phids) { $handles = $this->loadViewerHandles($phids); } $props = array(); if ($commit->getAuditStatus()) { $status = PhabricatorAuditCommitStatusConstants::getStatusName( $commit->getAuditStatus()); - $props['Status'] = phutil_render_tag( + $props['Status'] = phutil_tag( 'strong', array(), - phutil_escape_html($status)); + $status); } $props['Committed'] = phabricator_datetime($commit->getEpoch(), $user); $author_phid = $data->getCommitDetail('authorPHID'); if ($data->getCommitDetail('authorPHID')) { $props['Author'] = $handles[$author_phid]->renderLink(); } else { $props['Author'] = phutil_escape_html($data->getAuthorName()); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); } $committer = $data->getCommitDetail('committer'); if ($committer) { $committer_phid = $data->getCommitDetail('committerPHID'); if ($data->getCommitDetail('committerPHID')) { $props['Committer'] = $handles[$committer_phid]->renderLink(); } else { $props['Committer'] = phutil_escape_html($committer); } } $revision_phid = $data->getCommitDetail('differential.revisionPHID'); if ($revision_phid) { $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); } if ($parents) { $parent_links = array(); foreach ($parents as $parent) { $parent_links[] = $handles[$parent->getPHID()]->renderLink(); } $props['Parents'] = implode(' · ', $parent_links); } $request = $this->getDiffusionRequest(); $props['Branches'] = 'Unknown'; $props['Tags'] = 'Unknown'; $callsign = $request->getRepository()->getCallsign(); $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); Javelin::initBehavior( 'diffusion-commit-branches', array( $root.'/branches/' => 'commit-branches', $root.'/tags/' => 'commit-tags', )); $refs = $this->buildRefs($request); if ($refs) { $props['References'] = $refs; } if ($task_phids) { $task_list = array(); foreach ($task_phids as $phid) { $task_list[] = $handles[$phid]->renderLink(); } $task_list = implode('
    ', $task_list); $props['Tasks'] = $task_list; } if ($proj_phids) { $proj_list = array(); foreach ($proj_phids as $phid) { $proj_list[] = $handles[$phid]->renderLink(); } $proj_list = implode('
    ', $proj_list); $props['Projects'] = $proj_list; } return $props; } private function buildAuditTable( PhabricatorRepositoryCommit $commit, array $audits) { assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest'); $user = $this->getRequest()->getUser(); $view = new PhabricatorAuditListView(); $view->setAudits($audits); $view->setCommits(array($commit)); $view->setUser($user); $view->setShowDescriptions(false); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $view->setAuthorityPHIDs($this->auditAuthorityPHIDs); $this->highlightedAudits = $view->getHighlightedAudits(); $panel = new AphrontPanelView(); $panel->setHeader('Audits'); $panel->setCaption('Audits you are responsible for are highlighted.'); $panel->appendChild($view); $panel->setNoBackground(); return $panel; } private function buildComments(PhabricatorRepositoryCommit $commit) { $user = $this->getRequest()->getUser(); $comments = id(new PhabricatorAuditComment())->loadAllWhere( 'targetPHID = %s ORDER BY dateCreated ASC', $commit->getPHID()); $inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere( 'commitPHID = %s AND auditCommentID IS NOT NULL', $commit->getPHID()); $path_ids = mpull($inlines, 'getPathID'); $path_map = array(); if ($path_ids) { $path_map = id(new DiffusionPathQuery()) ->withPathIDs($path_ids) ->execute(); $path_map = ipull($path_map, 'path', 'id'); } $engine = new PhabricatorMarkupEngine(); $engine->setViewer($user); foreach ($comments as $comment) { $engine->addObject( $comment, PhabricatorAuditComment::MARKUP_FIELD_BODY); } foreach ($inlines as $inline) { $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); $view = new DiffusionCommentListView(); $view->setMarkupEngine($engine); $view->setUser($user); $view->setComments($comments); $view->setInlineComments($inlines); $view->setPathMap($path_map); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); return $view; } private function renderAddCommentPanel( PhabricatorRepositoryCommit $commit, array $audit_requests, $pane_id = null) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); $user = $this->getRequest()->getUser(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); Javelin::initBehavior( 'differential-keyboard-navigation', array( 'haunt' => $pane_id, )); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft = $draft->getDraft(); } else { $draft = null; } $actions = $this->getAuditActions($commit, $audit_requests); $form = id(new AphrontFormView()) ->setUser($user) ->setAction('/audit/addcomment/') ->addHiddenInput('commit', $commit->getPHID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Action') ->setName('action') ->setID('audit-action') ->setOptions($actions)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Add Auditors') ->setName('auditors') ->setControlID('add-auditors') ->setControlStyle('display: none') ->setID('add-auditors-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Add CCs') ->setName('ccs') ->setControlID('add-ccs') ->setControlStyle('display: none') ->setID('add-ccs-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel('Comments') ->setName('content') ->setValue($draft) ->setID('audit-content') ->setUser($user)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($is_serious ? 'Submit' : 'Cook the Books')); $panel = new AphrontPanelView(); $panel->setHeader($is_serious ? 'Audit Commit' : 'Creative Accounting'); $panel->appendChild($form); $panel->addClass('aphront-panel-accent'); $panel->addClass('aphront-panel-flush'); require_celerity_resource('phabricator-transaction-view-css'); Javelin::initBehavior( 'differential-add-reviewers-and-ccs', array( 'dynamic' => array( 'add-auditors-tokenizer' => array( 'actions' => array('add_auditors' => 1), 'src' => '/typeahead/common/users/', 'row' => 'add-auditors', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'placeholder' => 'Type a user name...', ), 'add-ccs-tokenizer' => array( 'actions' => array('add_ccs' => 1), 'src' => '/typeahead/common/mailable/', 'row' => 'add-ccs', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'placeholder' => 'Type a user or mailing list...', ), ), 'select' => 'audit-action', )); Javelin::initBehavior('differential-feedback-preview', array( 'uri' => '/audit/preview/'.$commit->getID().'/', 'preview' => 'audit-preview', 'content' => 'audit-content', 'action' => 'audit-action', 'previewTokenizers' => array( 'auditors' => 'add-auditors-tokenizer', 'ccs' => 'add-ccs-tokenizer', ), 'inline' => 'inline-comment-preview', 'inlineuri' => '/diffusion/inline/preview/'.$commit->getPHID().'/', )); $preview_panel = '
    Loading preview...
    '; return phutil_render_tag( 'div', array( 'class' => 'differential-add-comment-panel', ), $panel->render(). $preview_panel); } /** * Return a map of available audit actions for rendering into a