diff --git a/src/aphront/console/plugin/DarkConsoleServicesPlugin.php b/src/aphront/console/plugin/DarkConsoleServicesPlugin.php index 442ca9db4c..29e7c50d21 100644 --- a/src/aphront/console/plugin/DarkConsoleServicesPlugin.php +++ b/src/aphront/console/plugin/DarkConsoleServicesPlugin.php @@ -1,278 +1,277 @@ getServiceCallLog(); foreach ($log as $key => $entry) { $config = idx($entry, 'config', array()); unset($log[$key]['config']); if (empty($_REQUEST['__analyze__'])) { $log[$key]['explain'] = array( 'sev' => 7, 'size' => null, 'reason' => 'Disabled', ); // Query analysis is disabled for this request, so don't do any of it. continue; } if ($entry['type'] != 'query') { continue; } // For each SELECT query, go issue an EXPLAIN on it so we can flag stuff // causing table scans, etc. if (preg_match('/^\s*SELECT\b/i', $entry['query'])) { $conn = PhabricatorEnv::newObjectFromConfig( 'mysql.implementation', array($entry['config'])); try { $explain = queryfx_all( $conn, 'EXPLAIN %Q', $entry['query']); $badness = 0; $size = 1; $reason = null; foreach ($explain as $table) { $size *= (int)$table['rows']; switch ($table['type']) { case 'index': $cur_badness = 1; $cur_reason = 'Index'; break; case 'const': $cur_badness = 1; $cur_reason = 'Const'; break; case 'eq_ref'; $cur_badness = 2; $cur_reason = 'EqRef'; break; case 'range': $cur_badness = 3; $cur_reason = 'Range'; break; case 'ref': $cur_badness = 3; $cur_reason = 'Ref'; break; case 'fulltext': $cur_badness = 3; $cur_reason = 'Fulltext'; break; case 'ALL': if (preg_match('/Using where/', $table['Extra'])) { if ($table['rows'] < 256 && !empty($table['possible_keys'])) { $cur_badness = 2; $cur_reason = 'Small Table Scan'; } else { $cur_badness = 6; $cur_reason = 'TABLE SCAN!'; } } else { $cur_badness = 3; $cur_reason = 'Whole Table'; } break; default: if (preg_match('/No tables used/i', $table['Extra'])) { $cur_badness = 1; $cur_reason = 'No Tables'; } else if (preg_match('/Impossible/i', $table['Extra'])) { $cur_badness = 1; $cur_reason = 'Empty'; } else { $cur_badness = 4; $cur_reason = "Can't Analyze"; } break; } if ($cur_badness > $badness) { $badness = $cur_badness; $reason = $cur_reason; } } $log[$key]['explain'] = array( 'sev' => $badness, 'size' => $size, 'reason' => $reason, ); } catch (Exception $ex) { $log[$key]['explain'] = array( 'sev' => 5, 'size' => null, 'reason' => $ex->getMessage(), ); } } } return array( 'start' => PhabricatorStartup::getStartTime(), 'end' => microtime(true), 'log' => $log, ); } public function render() { $data = $this->getData(); $log = $data['log']; $results = array(); $results[] = '
'. phutil_tag( 'a', array( 'href' => $this->getRequestURI()->alter('__analyze__', true), 'class' => isset($_REQUEST['__analyze__']) ? 'disabled button' : 'green button', ), 'Analyze Query Plans'). '

Calls to External Services

'. '
'. '
'; $page_total = $data['end'] - $data['start']; $totals = array(); $counts = array(); foreach ($log as $row) { $totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration']; $counts[$row['type']] = idx($counts, $row['type'], 0) + 1; } $totals['All Services'] = array_sum($totals); $counts['All Services'] = array_sum($counts); $totals['Entire Page'] = $page_total; $counts['Entire Page'] = 0; $summary = array(); foreach ($totals as $type => $total) { $summary[] = array( $type, number_format($counts[$type]), number_format((int)(1000000 * $totals[$type])).' us', sprintf('%.1f%%', 100 * $totals[$type] / $page_total), ); } $summary_table = new AphrontTableView($summary); $summary_table->setColumnClasses( array( '', 'n', 'n', 'wide', )); $summary_table->setHeaders( array( 'Type', 'Count', 'Total Cost', 'Page Weight', )); $results[] = $summary_table->render(); $rows = array(); foreach ($log as $row) { $analysis = null; switch ($row['type']) { case 'query': $info = $row['query']; $info = wordwrap($info, 128, "\n", true); if (!empty($row['explain'])) { - $analysis = phutil_escape_html($row['explain']['reason']); - $analysis = phutil_render_tag( + $analysis = phutil_tag( 'span', array( 'class' => 'explain-sev-'.$row['explain']['sev'], ), - $analysis); + $row['explain']['reason']); } $info = phutil_escape_html($info); break; case 'connect': $info = $row['host'].':'.$row['database']; $info = phutil_escape_html($info); break; case 'exec': $info = $row['command']; $info = phutil_escape_html($info); break; case 'conduit': $info = $row['method']; $info = phutil_escape_html($info); break; case 'http': $info = $row['uri']; $info = phutil_escape_html($info); break; default: $info = '-'; break; } $rows[] = array( phutil_escape_html($row['type']), '+'.number_format(1000 * ($row['begin'] - $data['start'])).' ms', number_format(1000000 * $row['duration']).' us', $info, $analysis, ); } $table = new AphrontTableView($rows); $table->setColumnClasses( array( null, 'n', 'n', 'wide', '', )); $table->setHeaders( array( 'Event', 'Start', 'Duration', 'Details', 'Analysis', )); $results[] = $table->render(); return implode("\n", $results); } } diff --git a/src/aphront/response/AphrontRedirectResponse.php b/src/aphront/response/AphrontRedirectResponse.php index bb34a97d5b..254073390d 100644 --- a/src/aphront/response/AphrontRedirectResponse.php +++ b/src/aphront/response/AphrontRedirectResponse.php @@ -1,65 +1,65 @@ uri = $uri; return $this; } public function getURI() { return (string)$this->uri; } public function shouldStopForDebugging() { return PhabricatorEnv::getEnvConfig('debug.stop-on-redirect'); } public function getHeaders() { $headers = array(); if (!$this->shouldStopForDebugging()) { $headers[] = array('Location', $this->uri); } $headers = array_merge(parent::getHeaders(), $headers); return $headers; } public function buildResponseString() { if ($this->shouldStopForDebugging()) { $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->setApplicationName('Debug'); $view->setTitle('Stopped on Redirect'); $error = new AphrontErrorView(); $error->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $error->setTitle('Stopped on Redirect'); - $link = phutil_render_tag( + $link = phutil_tag( 'a', array( 'href' => $this->getURI(), ), - 'Continue to: '.phutil_escape_html($this->getURI())); + 'Continue to: '.$this->getURI()); $error->appendChild( '

You were stopped here because debug.stop-on-redirect '. 'is set in your configuration.

'. '

'.$link.'

'); $view->appendChild($error); return $view->render(); } return ''; } } diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php index 76f98c7eed..581d3544ae 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php @@ -1,226 +1,224 @@ channel = $data['channel']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $uri = clone $request->getRequestURI(); $uri->setQueryParams(array()); $pager = new AphrontCursorPagerView(); $pager->setURI($uri); $pager->setPageSize(250); $query = id(new PhabricatorChatLogQuery()) ->setViewer($user) ->withChannels(array($this->channel)); list($after, $before, $map) = $this->getPagingParameters($request, $query); $pager->setAfterID($after); $pager->setBeforeID($before); $logs = $query->executeWithCursorPager($pager); // Show chat logs oldest-first. $logs = array_reverse($logs); // Divide all the logs into blocks, where a block is the same author saying // several things in a row. A block ends when another user speaks, or when // two minutes pass without the author speaking. $blocks = array(); $block = null; $last_author = null; $last_epoch = null; foreach ($logs as $log) { $this_author = $log->getAuthor(); $this_epoch = $log->getEpoch(); // Decide whether we should start a new block or not. $new_block = ($this_author !== $last_author) || ($this_epoch - (60 * 2) > $last_epoch); if ($new_block) { if ($block) { $blocks[] = $block; } $block = array( 'id' => $log->getID(), 'epoch' => $this_epoch, 'author' => $this_author, 'logs' => array($log), ); } else { $block['logs'][] = $log; } $last_author = $this_author; $last_epoch = $this_epoch; } if ($block) { $blocks[] = $block; } // Figure out CSS classes for the blocks. We alternate colors between // lines, and highlight the entire block which contains the target ID or // date, if applicable. foreach ($blocks as $key => $block) { $classes = array(); if ($key % 2) { $classes[] = 'alternate'; } $ids = mpull($block['logs'], 'getID', 'getID'); if (array_intersect_key($ids, $map)) { $classes[] = 'highlight'; } $blocks[$key]['class'] = $classes ? implode(' ', $classes) : null; } require_celerity_resource('phabricator-chatlog-css'); $out = array(); $out[] = ''; foreach ($blocks as $block) { $author = $block['author']; $author = phutil_utf8_shorten($author, 18); - $author = phutil_escape_html($author); - $author = phutil_render_tag('td', array('class' => 'author'), $author); + $author = phutil_tag('td', array('class' => 'author'), $author); $message = mpull($block['logs'], 'getMessage'); $message = implode("\n", $message); - $message = phutil_escape_html($message); - $message = phutil_render_tag('td', array('class' => 'message'), $message); + $message = phutil_tag('td', array('class' => 'message'), $message); $href = $uri->alter('at', $block['id']); $timestamp = $block['epoch']; $timestamp = phabricator_datetime($timestamp, $user); - $timestamp = phutil_render_tag('a', array('href' => $href), $timestamp); - $timestamp = phutil_render_tag( + $timestamp = phutil_tag('a', array('href' => $href), $timestamp); + $timestamp = phutil_tag( 'td', array( 'class' => 'timestamp', ), $timestamp); - $out[] = phutil_render_tag( + $out[] = phutil_tag( 'tr', array( 'class' => $block['class'], ), - $author.$message.$timestamp); + array($author, $message, $timestamp)); } $out[] = '
'; $form = id(new AphrontFormView()) ->setUser($user) ->setMethod('GET') ->setAction($uri) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Date') ->setName('date') ->setValue($request->getStr('date'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Jump')); return $this->buildStandardPageResponse( array( '
', $form, '
', implode("\n", $out), $pager, '
', ), array( 'title' => 'Channel Log', )); } /** * From request parameters, figure out where we should jump to in the log. * We jump to either a date or log ID, but load a few lines of context before * it so the user can see the nearby conversation. */ private function getPagingParameters( AphrontRequest $request, PhabricatorChatLogQuery $query) { $user = $request->getUser(); $at_id = $request->getInt('at'); $at_date = $request->getStr('date'); $context_log = null; $map = array(); $query = clone $query; $query->setLimit(8); if ($at_id) { // Jump to the log in question, and load a few lines of context before // it. $context_logs = $query ->setAfterID($at_id) ->execute(); $context_log = last($context_logs); $map = array( $at_id => true, ); } else if ($at_date) { $timezone = new DateTimeZone($user->getTimezoneIdentifier()); try { $date = new DateTime($at_date, $timezone); $timestamp = $date->format('U'); } catch (Exception $e) { $timestamp = null; } if ($timestamp) { $context_logs = $query ->withMaximumEpoch($timestamp) ->execute(); $context_log = last($context_logs); $target_log = head($context_logs); if ($target_log) { $map = array( $target_log->getID() => true, ); } } } if ($context_log) { $after = null; $before = $context_log->getID() - 1; } else { $after = $request->getInt('after'); $before = $request->getInt('before'); } return array($after, $before, $map); } } diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 78b9f96e0c..c0b5dcab9f 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -1,503 +1,503 @@ key = $data['key']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if (empty($options[$this->key])) { // This may be a dead config entry, which existed in the past but no // longer exists. Allow it to be edited so it can be reviewed and // deleted. $option = id(new PhabricatorConfigOption()) ->setKey($this->key) ->setType('wild') ->setDefault(null) ->setDescription( pht( "This configuration option is unknown. It may be misspelled, ". "or have existed in a previous version of Phabricator.")); $group = null; $group_uri = $this->getApplicationURI(); } else { $option = $options[$this->key]; $group = $option->getGroup(); $group_uri = $this->getApplicationURI('group/'.$group->getKey().'/'); } $issue = $request->getStr('issue'); if ($issue) { // If the user came here from an open setup issue, send them back. $done_uri = $this->getApplicationURI('issue/'.$issue.'/'); } else { $done_uri = $group_uri; } // Check if the config key is already stored in the database. // Grab the value if it is. $config_entry = id(new PhabricatorConfigEntry()) ->loadOneWhere( 'configKey = %s AND namespace = %s', $this->key, 'default'); if (!$config_entry) { $config_entry = id(new PhabricatorConfigEntry()) ->setConfigKey($this->key) ->setNamespace('default') ->setIsDeleted(true); } $e_value = null; $errors = array(); if ($request->isFormPost() && !$option->getLocked()) { $result = $this->readRequest( $option, $request); list($e_value, $value_errors, $display_value, $xaction) = $result; $errors = array_merge($errors, $value_errors); if (!$errors) { $editor = id(new PhabricatorConfigEditor()) ->setActor($user) ->setContinueOnNoEffect(true) ->setContentSource( PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr(), ))); try { $editor->applyTransactions($config_entry, array($xaction)); return id(new AphrontRedirectResponse())->setURI($done_uri); } catch (PhabricatorConfigValidationException $ex) { $e_value = pht('Invalid'); $errors[] = $ex->getMessage(); } } } else { $display_value = $this->getDisplayValue($option, $config_entry); } $form = new AphrontFormView(); $form->setFlexible(true); $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('You broke everything!')) ->setErrors($errors); } else if ($option->getHidden()) { $msg = pht( "This configuration is hidden and can not be edited or viewed from ". "the web interface."); $error_view = id(new AphrontErrorView()) ->setTitle(pht('Configuration Hidden')) ->setSeverity(AphrontErrorView::SEVERITY_WARNING) ->appendChild('

'.phutil_escape_html($msg).'

'); } else if ($option->getLocked()) { $msg = pht( "This configuration is locked and can not be edited from the web ". "interface."); $error_view = id(new AphrontErrorView()) ->setTitle(pht('Configuration Locked')) ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->appendChild('

'.phutil_escape_html($msg).'

'); } if ($option->getHidden()) { $control = null; } else { $control = $this->renderControl( $option, $display_value, $e_value); } $engine = new PhabricatorMarkupEngine(); $engine->addObject($option, 'description'); $engine->process(); - $description = phutil_render_tag( + $description = phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $engine->getOutput($option, 'description')); $form ->setUser($user) ->addHiddenInput('issue', $request->getStr('issue')) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Description')) ->setValue($description)) ->appendChild($control); $submit_control = id(new AphrontFormSubmitControl()) ->addCancelButton($done_uri); if (!$option->getLocked()) { $submit_control->setValue(pht('Save Config Entry')); } $form->appendChild($submit_control); $examples = $this->renderExamples($option); if ($examples) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Examples')) ->setValue($examples)); } if (!$option->getHidden()) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Default')) ->setValue($this->renderDefaults($option))); } $title = pht('Edit %s', $this->key); $short = pht('Edit'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Config')) ->setHref($this->getApplicationURI())); if ($group) { $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($group->getName()) ->setHref($group_uri)); } $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($this->key) ->setHref('/config/edit/'.$this->key)); $xactions = id(new PhabricatorConfigTransactionQuery()) ->withObjectPHIDs(array($config_entry->getPHID())) ->setViewer($user) ->execute(); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setTransactions($xactions); return $this->buildApplicationPage( array( $crumbs, id(new PhabricatorHeaderView())->setHeader($title), $error_view, $form, $xaction_view, ), array( 'title' => $title, 'device' => true, )); } private function readRequest( PhabricatorConfigOption $option, AphrontRequest $request) { $xaction = new PhabricatorConfigTransaction(); $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); $e_value = null; $errors = array(); $value = $request->getStr('value'); if (!strlen($value)) { $value = null; $xaction->setNewValue( array( 'deleted' => true, 'value' => null, )); return array($e_value, $errors, $value, $xaction); } $type = $option->getType(); $set_value = null; switch ($type) { case 'int': if (preg_match('/^-?[0-9]+$/', trim($value))) { $set_value = (int)$value; } else { $e_value = pht('Invalid'); $errors[] = pht('Value must be an integer.'); } break; case 'string': case 'enum': $set_value = (string)$value; break; case 'list': $set_value = $request->getStrList('value'); break; case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; case 'bool': switch ($value) { case 'true': $set_value = true; break; case 'false': $set_value = false; break; default: $e_value = pht('Invalid'); $errors[] = pht('Value must be boolean, "true" or "false".'); break; } break; case 'class': if (!class_exists($value)) { $e_value = pht('Invalid'); $errors[] = pht('Class does not exist.'); } else { $base = $option->getBaseClass(); if (!is_subclass_of($value, $base)) { $e_value = pht('Invalid'); $errors[] = pht('Class is not of valid type.'); } else { $set_value = $value; } } break; default: $json = json_decode($value, true); if ($json === null && strtolower($value) != 'null') { $e_value = pht('Invalid'); $errors[] = pht( 'The given value must be valid JSON. This means, among '. 'other things, that you must wrap strings in double-quotes.'); } else { $set_value = $json; } break; } if (!$errors) { $xaction->setNewValue( array( 'deleted' => false, 'value' => $set_value, )); } else { $xaction = null; } return array($e_value, $errors, $value, $xaction); } private function getDisplayValue( PhabricatorConfigOption $option, PhabricatorConfigEntry $entry) { if ($entry->getIsDeleted()) { return null; } $type = $option->getType(); $value = $entry->getValue(); switch ($type) { case 'int': case 'string': case 'enum': return $value; case 'bool': return $value ? 'true' : 'false'; case 'list': return implode("\n", nonempty($value, array())); case 'set': return implode("\n", nonempty(array_keys($value), array())); default: return PhabricatorConfigJSON::prettyPrintJSON($value); } } private function renderControl( PhabricatorConfigOption $option, $display_value, $e_value) { $type = $option->getType(); switch ($type) { case 'int': case 'string': $control = id(new AphrontFormTextControl()); break; case 'bool': $control = id(new AphrontFormSelectControl()) ->setOptions( array( '' => pht('(Use Default)'), 'true' => idx($option->getBoolOptions(), 0), 'false' => idx($option->getBoolOptions(), 1), )); break; case 'enum': $options = array_mergev( array( array('' => pht('(Use Default)')), $option->getEnumOptions(), )); $control = id(new AphrontFormSelectControl()) ->setOptions($options); break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') ->setAncestorClass($option->getBaseClass()) ->setConcreteOnly(true) ->selectSymbolsWithoutLoading(); $names = ipull($symbols, 'name', 'name'); asort($names); $names = array( '' => pht('(Use Default)'), ) + $names; $control = id(new AphrontFormSelectControl()) ->setOptions($names); break; case 'list': case 'set': $control = id(new AphrontFormTextAreaControl()) ->setCaption(pht('Separate values with newlines or commas.')); break; default: $control = id(new AphrontFormTextAreaControl()) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setCustomClass('PhabricatorMonospaced') ->setCaption(pht('Enter value in JSON.')); break; } $control ->setLabel('Value') ->setError($e_value) ->setValue($display_value) ->setName('value'); if ($option->getLocked()) { $control->setDisabled(true); } return $control; } private function renderExamples(PhabricatorConfigOption $option) { $examples = $option->getExamples(); if (!$examples) { return null; } $table = array(); $table[] = ''; $table[] = ''.pht('Example').''; $table[] = ''.pht('Value').''; $table[] = ''; foreach ($examples as $example) { list($value, $description) = $example; if ($value === null) { $value = ''.pht('(empty)').''; } else { $value = nl2br(phutil_escape_html($value)); } $table[] = ''; $table[] = ''.phutil_escape_html($description).''; $table[] = ''.$value.''; $table[] = ''; } require_celerity_resource('config-options-css'); - return phutil_render_tag( + return phutil_tag( 'table', array( 'class' => 'config-option-table', ), - implode("\n", $table)); + new PhutilSafeHTML(implode("\n", $table))); } private function renderDefaults(PhabricatorConfigOption $option) { $stack = PhabricatorEnv::getConfigSourceStack(); $stack = $stack->getStack(); /* TODO: Once DatabaseSource lands, do this: foreach ($stack as $key => $source) { unset($stack[$key]); if ($source instanceof PhabricatorConfigDatabaseSource) { break; } } */ $table = array(); $table[] = ''; $table[] = ''.pht('Source').''; $table[] = ''.pht('Value').''; $table[] = ''; foreach ($stack as $key => $source) { $value = $source->getKeys( array( $option->getKey(), )); if (!array_key_exists($option->getKey(), $value)) { $value = ''.pht('(empty)').''; } else { $value = PhabricatorConfigJSON::prettyPrintJSON( $value[$option->getKey()]); } $table[] = ''; $table[] = ''.phutil_escape_html($source->getName()).''; $table[] = ''.$value.''; $table[] = ''; } require_celerity_resource('config-options-css'); - return phutil_render_tag( + return phutil_tag( 'table', array( 'class' => 'config-option-table', ), - implode("\n", $table)); + new PhutilSafeHTML(implode("\n", $table))); } } diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index a41bc4efdb..2f53b30496 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -1,117 +1,119 @@ groupKey = $data['key']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $groups = PhabricatorApplicationConfigOptions::loadAll(); $options = idx($groups, $this->groupKey); if (!$options) { return new Aphront404Response(); } $title = pht('%s Configuration', $options->getName()); $header = id(new PhabricatorHeaderView()) ->setHeader($title); $list = $this->buildOptionList($options->getOptions()); $crumbs = $this ->buildApplicationCrumbs() ->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Config')) ->setHref($this->getApplicationURI())) ->addCrumb( id(new PhabricatorCrumbView()) ->setName($options->getName()) ->setHref($this->getApplicationURI())); return $this->buildApplicationPage( array( $crumbs, $header, $list, ), array( 'title' => $title, 'device' => true, ) ); } private function buildOptionList(array $options) { assert_instances_of($options, 'PhabricatorConfigOption'); require_celerity_resource('config-options-css'); $db_values = array(); if ($options) { $db_values = id(new PhabricatorConfigEntry())->loadAllWhere( 'configKey IN (%Ls) AND namespace = %s', mpull($options, 'getKey'), 'default'); $db_values = mpull($db_values, null, 'getConfigKey'); } $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getRequest()->getUser()); foreach ($options as $option) { $engine->addObject($option, 'summary'); } $engine->process(); $list = new PhabricatorObjectItemListView(); foreach ($options as $option) { $summary = $engine->getOutput($option, 'summary'); $item = id(new PhabricatorObjectItemView()) ->setHeader($option->getKey()) ->setHref('/config/edit/'.$option->getKey().'/') ->addAttribute($summary); if (!$option->getHidden() && !$option->getMasked()) { $current_value = PhabricatorEnv::getEnvConfig($option->getKey()); $current_value = PhabricatorConfigJSON::prettyPrintJSON( $current_value); - $current_value = phutil_render_tag( + $current_value = phutil_tag( 'div', array( 'class' => 'config-options-current-value', ), - ''.pht('Current Value:').' '. - phutil_escape_html($current_value)); + array( + phutil_tag('span', array(), pht('Current Value:')), + ' '.$current_value, + )); $item->appendChild($current_value); } $db_value = idx($db_values, $option->getKey()); if ($db_value && !$db_value->getIsDeleted()) { $item->addIcon('edit', pht('Customized')); } if ($option->getHidden()) { $item->addIcon('unpublish', pht('Hidden')); } else if ($option->getMasked()) { $item->addIcon('unpublish-grey', pht('Masked')); } else if ($option->getLocked()) { $item->addIcon('lock', pht('Locked')); } $list->addItem($item); } return $list; } } diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index 5ac904e99a..795f124b9d 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -1,333 +1,330 @@ issue = $issue; return $this; } public function getIssue() { return $this->issue; } public function render() { $issue = $this->getIssue(); - $description = phutil_render_tag( + $description = phutil_tag( 'div', array( 'class' => 'setup-issue-instructions', ), - nl2br(phutil_escape_html($issue->getMessage()))); + new PhutilSafeHTML(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( + $description .= phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), - phutil_render_tag('p', array(), $run_these). - phutil_render_tag('pre', array(), implode("\n", $commands))); + array( + 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( + $description .= phutil_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)); + array( + 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_tag( 'div', array( 'class' => 'setup-issue-next', ), pht('To continue, resolve this problem and reload the page.')); $name = phutil_tag( 'div', array( 'class' => 'setup-issue-name', ), $issue->getName()); - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'setup-issue', ), - $name.$description.$next); + array($name, $description, $next)); } private function renderPhabricatorConfig(array $configs) { $issue = $this->getIssue(); - $table_info = phutil_render_tag( + $table_info = phutil_tag( 'p', array(), pht( "The current Phabricator configuration has these %d value(s):", count($configs))); $table = array(); foreach ($configs as $key) { $table[] = ''; $table[] = ''.phutil_escape_html($key).''; $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[] = ''.$value.''; $table[] = ''; } - $table = phutil_render_tag( + $table = phutil_tag( 'table', array( ), - implode("\n", $table)); + new PhutilSafeHTML(implode("\n", $table))); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if ($this->getIssue()->getIsFatal()) { - $update_info = phutil_render_tag( + $update_info = phutil_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( + $link = phutil_tag( 'a', array( 'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(), ), - pht('Edit %s', phutil_escape_html($config))); + pht('Edit %s', $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( + $table_info = phutil_tag( 'p', array(), pht( "The current PHP configuration has these %d value(s):", count($configs))); $table = array(); foreach ($configs as $key) { $table[] = ''; $table[] = ''.phutil_escape_html($key).''; $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[] = ''.$value.''; $table[] = ''; } - $table = phutil_render_tag( + $table = phutil_tag( 'table', - array( - - ), - implode("\n", $table)); + array(), + new PhutilSafeHTML(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( + $info = phutil_tag( 'p', array(), pht( "To update these %d value(s), edit your PHP configuration file.", count($configs))); } else { - $info = phutil_render_tag( + $info = phutil_tag( 'p', array(), pht( "To update these %d value(s), edit your PHP configuration file, ". "located here:", count($configs))); $info .= phutil_tag( 'pre', array(), $ini_loc); } if ($more_loc) { - $info .= phutil_render_tag( + $info .= phutil_tag( 'p', array(), pht( "PHP also loaded these configuration file(s):", count($more_loc))); $info .= phutil_tag( 'pre', array(), implode("\n", $more_loc)); } - $info .= phutil_render_tag( + $info .= phutil_tag( 'p', array(), - pht( - "You can find more information about PHP configuration values in the ". - "%s.", - phutil_tag( - 'a', - array( - 'href' => 'http://php.net/manual/ini.list.php', - ), - pht('PHP Documentation')))); + phutil_safe_html(pht( + 'You can find more information about PHP configuration values in the '. + 'PHP Documentation.', + 'http://php.net/manual/ini.list.php'))); - $info .= phutil_render_tag( + $info .= phutil_tag( 'p', array(), - pht( + phutil_safe_html(pht( "After editing the PHP configuration, restart your ". - "webserver for the changes to take effect.")); + "webserver for the changes to take effect."))); - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), - $table_info.$table.$info); + array($table_info, $table, $info)); } } diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php index 35f234733a..c60b584209 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php @@ -1,119 +1,119 @@ daemonLogs = $daemon_logs; return $this; } public function render() { $rows = array(); if (!$this->user) { throw new Exception("Call setUser() before rendering!"); } foreach ($this->daemonLogs as $log) { $epoch = $log->getDateCreated(); $status = $log->getStatus(); if ($log->getHost() == php_uname('n') && $status != PhabricatorDaemonLog::STATUS_EXITED && $status != PhabricatorDaemonLog::STATUS_DEAD) { $pid = $log->getPID(); $is_running = PhabricatorDaemonReference::isProcessRunning($pid); if (!$is_running) { $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); $log->setStatus(PhabricatorDaemonLog::STATUS_DEAD); $log->save(); unset($guard); $status = PhabricatorDaemonLog::STATUS_DEAD; } } $heartbeat_timeout = $log->getDateModified() + 3 * PhutilDaemonOverseer::HEARTBEAT_WAIT; if ($status == PhabricatorDaemonLog::STATUS_RUNNING && $heartbeat_timeout < time()) { $status = PhabricatorDaemonLog::STATUS_UNKNOWN; } switch ($status) { case PhabricatorDaemonLog::STATUS_RUNNING: $style = 'color: #00cc00'; $title = 'Running'; $symbol = '•'; break; case PhabricatorDaemonLog::STATUS_DEAD: $style = 'color: #cc0000'; $title = 'Died'; $symbol = '•'; break; case PhabricatorDaemonLog::STATUS_EXITED: $style = 'color: #000000'; $title = 'Exited'; $symbol = '•'; break; case PhabricatorDaemonLog::STATUS_UNKNOWN: default: // fallthrough $style = 'color: #888888'; $title = 'Unknown'; $symbol = '?'; } - $running = phutil_render_tag( + $running = phutil_tag( 'span', array( 'style' => $style, 'title' => $title, ), $symbol); $rows[] = array( $running, phutil_escape_html($log->getDaemon()), phutil_escape_html($log->getHost()), $log->getPID(), phabricator_date($epoch, $this->user), phabricator_time($epoch, $this->user), phutil_tag( 'a', array( 'href' => '/daemon/log/'.$log->getID().'/', 'class' => 'button small grey', ), 'View Log'), ); } $daemon_table = new AphrontTableView($rows); $daemon_table->setHeaders( array( '', 'Daemon', 'Host', 'PID', 'Date', 'Time', 'View', )); $daemon_table->setColumnClasses( array( '', 'wide wrap', '', '', '', 'right', 'action', )); return $daemon_table->render(); } } diff --git a/src/applications/differential/field/specification/DifferentialLintFieldSpecification.php b/src/applications/differential/field/specification/DifferentialLintFieldSpecification.php index b2a9a4c943..2c0164a255 100644 --- a/src/applications/differential/field/specification/DifferentialLintFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialLintFieldSpecification.php @@ -1,268 +1,268 @@ renderLabelForRevisionView(); } public function renderValueForDiffView() { return $this->renderValueForRevisionView(); } public function shouldAppearOnRevisionView() { return true; } public function renderLabelForRevisionView() { return 'Lint:'; } private function getLintExcuse() { return $this->getDiffProperty('arc:lint-excuse'); } private function getPostponedLinters() { return $this->getDiffProperty('arc:lint-postponed'); } public function renderValueForRevisionView() { $diff = $this->getManualDiff(); $path_changesets = mpull($diff->loadChangesets(), 'getID', 'getFilename'); $lstar = DifferentialRevisionUpdateHistoryView::renderDiffLintStar($diff); $lmsg = DifferentialRevisionUpdateHistoryView::getDiffLintMessage($diff); $ldata = $this->getDiffProperty('arc:lint'); $ltail = null; $rows = array(); $rows[] = array( 'style' => 'star', 'name' => $lstar, 'value' => $lmsg, 'show' => true, ); $excuse = $this->getLintExcuse(); if ($excuse) { $rows[] = array( 'style' => 'excuse', 'name' => 'Excuse', 'value' => nl2br(phutil_escape_html($excuse)), 'show' => true, ); } $show_limit = 10; $hidden = array(); if ($ldata) { $ldata = igroup($ldata, 'path'); foreach ($ldata as $path => $messages) { $rows[] = array( 'style' => 'section', 'name' => phutil_escape_html($path), 'show' => $show_limit, ); foreach ($messages as $message) { $path = idx($message, 'path'); $line = idx($message, 'line'); $code = idx($message, 'code'); $severity = idx($message, 'severity'); $name = idx($message, 'name'); $description = idx($message, 'description'); $line_link = 'line '.intval($line); if (isset($path_changesets[$path])) { $href = '#C'.$path_changesets[$path].'NL'.max(1, $line); if ($diff->getID() != $this->getDiff()->getID()) { $href = '/D'.$diff->getRevisionID().'?id='.$diff->getID().$href; } - $line_link = phutil_render_tag( + $line_link = phutil_tag( 'a', array( 'href' => $href, ), $line_link); } if ($show_limit) { --$show_limit; $show = true; } else { $show = false; if (empty($hidden[$severity])) { $hidden[$severity] = 0; } $hidden[$severity]++; } $rows[] = array( 'style' => $this->getSeverityStyle($severity), 'name' => phutil_escape_html(ucwords($severity)), 'value' => hsprintf( "(%s) %s at {$line_link}", $code, $name), 'show' => $show, ); if (isset($message['locations'])) { $locations = array(); foreach ($message['locations'] as $location) { $other_line = idx($location, 'line'); $locations[] = idx($location, 'path', $path). ($other_line ? ":{$other_line}" : ""); } $description .= "\nOther locations: ".implode(", ", $locations); } if (strlen($description)) { $rows[] = array( 'style' => 'details', 'value' => nl2br(phutil_escape_html($description)), 'show' => false, ); if (empty($hidden['details'])) { $hidden['details'] = 0; } $hidden['details']++; } } } } $postponed = $this->getPostponedLinters(); if ($postponed) { foreach ($postponed as $linter) { $rows[] = array( 'style' => $this->getPostponedStyle(), 'name' => 'Postponed', 'value' => phutil_escape_html($linter), 'show' => false, ); if (empty($hidden['postponed'])) { $hidden['postponed'] = 0; } $hidden['postponed']++; } } $show_string = $this->renderShowString($hidden); $view = new DifferentialResultsTableView(); $view->setRows($rows); $view->setShowMoreString($show_string); return $view->render(); } private function getSeverityStyle($severity) { $map = array( ArcanistLintSeverity::SEVERITY_ERROR => 'red', ArcanistLintSeverity::SEVERITY_WARNING => 'yellow', ArcanistLintSeverity::SEVERITY_AUTOFIX => 'yellow', ArcanistLintSeverity::SEVERITY_ADVICE => 'yellow', ); return idx($map, $severity); } private function getPostponedStyle() { return 'blue'; } private function renderShowString(array $hidden) { if (!$hidden) { return null; } // Reorder hidden things by severity. $hidden = array_select_keys( $hidden, array( ArcanistLintSeverity::SEVERITY_ERROR, ArcanistLintSeverity::SEVERITY_WARNING, ArcanistLintSeverity::SEVERITY_AUTOFIX, ArcanistLintSeverity::SEVERITY_ADVICE, 'details', 'postponed', )) + $hidden; $show = array(); foreach ($hidden as $key => $value) { switch ($key) { case ArcanistLintSeverity::SEVERITY_ERROR: $show[] = pht('%d Error(s)', $value); break; case ArcanistLintSeverity::SEVERITY_WARNING: $show[] = pht('%d Warning(s)', $value); break; case ArcanistLintSeverity::SEVERITY_AUTOFIX: $show[] = pht('%d Auto-Fix(es)', $value); break; case ArcanistLintSeverity::SEVERITY_ADVICE: $show[] = pht('%d Advice(s)', $value); break; case 'details': $show[] = pht('%d Detail(s)', $value); break; case 'postponed': $show[] = pht('%d Postponed', $value); break; default: $show[] = $value; break; } } return "Show Full Lint Results (".implode(', ', $show).")"; } public function renderWarningBoxForRevisionAccept() { $status = $this->getDiff()->getLintStatus(); if ($status < DifferentialLintStatus::LINT_WARN) { return null; } $severity = AphrontErrorView::SEVERITY_ERROR; $titles = array( DifferentialLintStatus::LINT_WARN => 'Lint Warning', DifferentialLintStatus::LINT_FAIL => 'Lint Failure', DifferentialLintStatus::LINT_SKIP => 'Lint Skipped', DifferentialLintStatus::LINT_POSTPONED => 'Lint Postponed', ); if ($status == DifferentialLintStatus::LINT_SKIP) { $content = "

    This diff was created without running lint. Make sure you are ". "OK with that before you accept this diff.

    "; } else if ($status == DifferentialLintStatus::LINT_POSTPONED) { $severity = AphrontErrorView::SEVERITY_WARNING; $content = "

    Postponed linters didn't finish yet. Make sure you are OK with ". "that before you accept this diff.

    "; } else { $content = "

    This diff has Lint Problems. Make sure you are OK with them ". "before you accept this diff.

    "; } return id(new AphrontErrorView()) ->setSeverity($severity) ->appendChild($content) ->setTitle(idx($titles, $status, 'Warning')); } } diff --git a/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php b/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php index 8900aef7f8..b4e89b3fa9 100644 --- a/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php @@ -1,223 +1,223 @@ renderLabelForRevisionView(); } public function renderValueForDiffView() { return $this->renderValueForRevisionView(); } public function shouldAppearOnRevisionView() { return true; } public function renderLabelForRevisionView() { return 'Unit:'; } private function getUnitExcuse() { return $this->getDiffProperty('arc:unit-excuse'); } public function renderValueForRevisionView() { $diff = $this->getManualDiff(); $ustar = DifferentialRevisionUpdateHistoryView::renderDiffUnitStar($diff); $umsg = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff); $rows = array(); $rows[] = array( 'style' => 'star', 'name' => $ustar, 'value' => $umsg, 'show' => true, ); $excuse = $this->getUnitExcuse(); if ($excuse) { $rows[] = array( 'style' => 'excuse', 'name' => 'Excuse', 'value' => nl2br(phutil_escape_html($excuse)), 'show' => true, ); } $show_limit = 10; $hidden = array(); $udata = $this->getDiffProperty('arc:unit'); if ($udata) { $sort_map = array( ArcanistUnitTestResult::RESULT_BROKEN => 0, ArcanistUnitTestResult::RESULT_FAIL => 1, ArcanistUnitTestResult::RESULT_UNSOUND => 2, ArcanistUnitTestResult::RESULT_SKIP => 3, ArcanistUnitTestResult::RESULT_POSTPONED => 4, ArcanistUnitTestResult::RESULT_PASS => 5, ); foreach ($udata as $key => $test) { $udata[$key]['sort'] = idx($sort_map, idx($test, 'result')); } $udata = isort($udata, 'sort'); foreach ($udata as $test) { $result = idx($test, 'result'); $default_hide = false; switch ($result) { case ArcanistUnitTestResult::RESULT_POSTPONED: case ArcanistUnitTestResult::RESULT_PASS: $default_hide = true; break; } if ($show_limit && !$default_hide) { --$show_limit; $show = true; } else { $show = false; if (empty($hidden[$result])) { $hidden[$result] = 0; } $hidden[$result]++; } - $value = phutil_escape_html(idx($test, 'name')); + $value = idx($test, 'name'); if (!empty($test['link'])) { - $value = phutil_render_tag( + $value = phutil_tag( 'a', array( 'href' => $test['link'], 'target' => '_blank', ), $value); } $rows[] = array( 'style' => $this->getResultStyle($result), 'name' => phutil_escape_html(ucwords($result)), 'value' => $value, 'show' => $show, ); $userdata = idx($test, 'userdata'); if ($userdata) { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $userdata = $engine->markupText($userdata); $rows[] = array( 'style' => 'details', 'value' => $userdata, 'show' => false, ); if (empty($hidden['details'])) { $hidden['details'] = 0; } $hidden['details']++; } } } $show_string = $this->renderShowString($hidden); $view = new DifferentialResultsTableView(); $view->setRows($rows); $view->setShowMoreString($show_string); return $view->render(); } private function getResultStyle($result) { $map = array( ArcanistUnitTestResult::RESULT_PASS => 'green', ArcanistUnitTestResult::RESULT_FAIL => 'red', ArcanistUnitTestResult::RESULT_SKIP => 'blue', ArcanistUnitTestResult::RESULT_BROKEN => 'red', ArcanistUnitTestResult::RESULT_UNSOUND => 'yellow', ArcanistUnitTestResult::RESULT_POSTPONED => 'blue', ); return idx($map, $result); } private function renderShowString(array $hidden) { if (!$hidden) { return null; } // Reorder hidden things by severity. $hidden = array_select_keys( $hidden, array( ArcanistUnitTestResult::RESULT_BROKEN, ArcanistUnitTestResult::RESULT_FAIL, ArcanistUnitTestResult::RESULT_UNSOUND, ArcanistUnitTestResult::RESULT_SKIP, ArcanistUnitTestResult::RESULT_POSTPONED, ArcanistUnitTestResult::RESULT_PASS, 'details', )) + $hidden; $noun = array( ArcanistUnitTestResult::RESULT_BROKEN => 'Broken', ArcanistUnitTestResult::RESULT_FAIL => 'Failed', ArcanistUnitTestResult::RESULT_UNSOUND => 'Unsound', ArcanistUnitTestResult::RESULT_SKIP => 'Skipped', ArcanistUnitTestResult::RESULT_POSTPONED => 'Postponed', ArcanistUnitTestResult::RESULT_PASS => 'Passed', ); $show = array(); foreach ($hidden as $key => $value) { if ($key == 'details') { $show[] = pht('%d Detail(s)', $value); } else { $show[] = $value.' '.idx($noun, $key); } } return "Show Full Unit Results (".implode(', ', $show).")"; } public function renderWarningBoxForRevisionAccept() { $diff = $this->getDiff(); $unit_warning = null; if ($diff->getUnitStatus() >= DifferentialUnitStatus::UNIT_WARN) { $titles = array( DifferentialUnitStatus::UNIT_WARN => 'Unit Tests Warning', DifferentialUnitStatus::UNIT_FAIL => 'Unit Tests Failure', DifferentialUnitStatus::UNIT_SKIP => 'Unit Tests Skipped', DifferentialUnitStatus::UNIT_POSTPONED => 'Unit Tests Postponed' ); if ($diff->getUnitStatus() == DifferentialUnitStatus::UNIT_POSTPONED) { $content = "

    This diff has postponed unit tests. The results should be ". "coming in soon. You should probably wait for them before accepting ". "this diff.

    "; } else if ($diff->getUnitStatus() == DifferentialUnitStatus::UNIT_SKIP) { $content = "

    Unit tests were skipped when this diff was created. Make sure ". "you are OK with that before you accept this diff.

    "; } else { $content = "

    This diff has Unit Test Problems. Make sure you are OK with ". "them before you accept this diff.

    "; } $unit_warning = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_ERROR) ->appendChild($content) ->setTitle(idx($titles, $diff->getUnitStatus(), 'Warning')); } return $unit_warning; } } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index fc969acd94..acea5b060d 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -1,317 +1,317 @@ 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'); $changesets = $this->changesets; Javelin::initBehavior('differential-toggle-files', array()); $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); $prefs = $this->user->loadPreferences(); $pref_symbols = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS); $detail->setChangeset($changeset); $detail->addButton($view_options); if ($pref_symbols != 'disabled') { $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_render_tag( 'a', array( 'href' => '#'.$uniq_id, 'meta' => array( 'id' => $uniq_id, 'ref' => $ref, 'kill' => true, ), 'sigil' => 'differential-load', 'mustcapture' => true, ), 'Load'); } $detail->appendChild( - phutil_render_tag( + phutil_tag( 'div', array( 'id' => $uniq_id, ), - '
    '.$load.'
    ')); + phutil_tag('div', array('class' => 'differential-loading'), $load))); $output[] = $detail->render(); } require_celerity_resource('aphront-tooltip-css'); Javelin::initBehavior('differential-populate', array( 'registry' => $mapping, 'whitespace' => $this->whitespace, 'uri' => $this->renderURI, )); Javelin::initBehavior('differential-show-more', array( 'uri' => $this->renderURI, 'whitespace' => $this->whitespace, )); Javelin::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', )); } return id(new PhabricatorHeaderView()) ->setHeader($this->getTitle()) ->render(). phutil_render_tag( 'div', array( 'class' => 'differential-review-stage', 'id' => 'differential-review-stage', ), implode("\n", $output)); } /** * Render the "Undo" markup for the inline comment undo feature. */ private function renderUndoTemplates() { $link = javelin_render_tag( 'a', array( 'href' => '#', 'sigil' => 'differential-inline-comment-undo', ), 'Undo'); - $div = phutil_render_tag( + $div = phutil_tag( 'div', array( 'class' => 'differential-inline-undo', ), - 'Changes discarded. '.$link); + array('Changes discarded. ', $link)); $template = ''. ''. ''. '
    %s%s
    '; return array( 'l' => sprintf($template, $div, ''), 'r' => sprintf($template, '', $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) { $meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath( $changeset->getAbsoluteRepositoryPath($repository, $this->diff), idx($changeset->getMetadata(), 'line:first'), $this->getBranch()); } $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(); Javelin::initBehavior( 'differential-dropdown-menus', array()); return javelin_render_tag( 'a', array( 'class' => 'button small grey', 'meta' => $meta, 'href' => idx($meta, 'detailURI', '#'), 'target' => '_blank', 'sigil' => 'differential-view-options', ), "View Options \xE2\x96\xBC"); } } diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php index 2c5d5058af..818522b2ba 100644 --- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php +++ b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php @@ -1,272 +1,272 @@ 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'); $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); $link = $this->renderChangesetLink($changeset, $ref); 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[] = phutil_escape_html($path); } $meta = implode('
    ', $meta); } else { if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { $meta = pht('Moved to %s', phutil_escape_html(reset($away))); } else { $meta = pht('Copied to %s', phutil_escape_html(reset($away))); } } } else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) { $meta = pht('Moved from %s', phutil_escape_html($changeset->getOldFile())); } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { $meta = pht('Copied from %s', phutil_escape_html($changeset->getOldFile())); } else { $meta = null; } $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 : 'M'; $fname = $changeset->getFilename(); $cov = $this->renderCoverage($coverage, $fname); if ($cov === null) { $mcov = $cov = '-'; } else { - $mcov = phutil_render_tag( + $mcov = phutil_tag( 'div', array( 'id' => 'differential-mcoverage-'.md5($fname), 'class' => 'differential-mcoverage-loading', ), (isset($this->visibleChangesets[$id]) ? 'Loading...' : '?')); } $rows[] = ''. phutil_render_tag( 'td', array( 'class' => 'differential-toc-char', 'title' => $chartitle, ), $char). ''.$pchar.''. ''.$desc.''. ''.$link.$lines.''. ''.$cov.''. ''.$mcov.''. ''; if ($meta) { $rows[] = ''. ''. ''.$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_render_tag( 'a', array( 'sigil' => 'differential-reveal-all', 'mustcapture' => true, 'class' => 'button differential-toc-reveal-all', ), pht('Show All Context') ); $buttons = ''. $editor_link.$reveal_link. ''; return id(new PhabricatorAnchorView()) ->setAnchorName('toc') ->setNavigationMarker(true) ->render(). id(new PhabricatorHeaderView()) ->setHeader(pht('Table of Contents')) ->render(). '
    '. ''. ''. ''. ''. ''. ''. ''. ''. ''. implode("\n", $rows). $buttons. '
    Path'. pht('Coverage (All)'). ''. pht('Coverage (Touched)'). '
    '. '
    '; } 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 = $changeset->getDisplayFilename(); return javelin_render_tag( 'a', array( 'href' => '#'.$changeset->getAnchorName(), 'meta' => array( 'id' => 'diff-'.$changeset->getAnchorName(), 'ref' => $ref, ), 'sigil' => 'differential-load', ), phutil_escape_html($display_file)); } } diff --git a/src/applications/differential/view/DifferentialRevisionCommentView.php b/src/applications/differential/view/DifferentialRevisionCommentView.php index 0670e87bdb..251c6d12fd 100644 --- a/src/applications/differential/view/DifferentialRevisionCommentView.php +++ b/src/applications/differential/view/DifferentialRevisionCommentView.php @@ -1,289 +1,289 @@ 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'); $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 = '
    '. $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()); $verb = phutil_escape_html($verb); $actions = array(); // TODO: i18n switch ($comment->getAction()) { case DifferentialAction::ACTION_ADDCCS: $actions[] = "{$author_link} added CCs: ". $this->renderHandleList($added_ccs)."."; $added_ccs = null; break; case DifferentialAction::ACTION_ADDREVIEWERS: $actions[] = "{$author_link} added reviewers: ". $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_render_tag( + $diff_link = phutil_tag( 'a', array( 'href' => '/D'.$comment->getRevisionID().'?id='.$diff_id, ), - 'Diff #'.phutil_escape_html($diff_id)); + 'Diff #'.$diff_id); $actions[] = "{$author_link} updated this revision to {$diff_link}."; } else { $actions[] = "{$author_link} {$verb} this revision."; } break; default: $actions[] = "{$author_link} {$verb} this revision."; break; } if ($added_reviewers) { $actions[] = "{$author_link} added reviewers: ". $this->renderHandleList($added_reviewers)."."; } if ($removed_reviewers) { $actions[] = "{$author_link} removed reviewers: ". $this->renderHandleList($removed_reviewers)."."; } if ($added_ccs) { $actions[] = "{$author_link} added CCs: ". $this->renderHandleList($added_ccs)."."; } foreach ($actions as $key => $action) { $actions[$key] = '
    '.$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( '
    '. $content. '
    '. $this->renderSingleView($inline_render)); } return $xaction_view->render(); } private function renderHandleList(array $phids) { $result = array(); foreach ($phids as $phid) { $result[] = $this->handles[$phid]->renderLink(); } return implode(', ', $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/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index b0565ca6ce..62d247a651 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1,121 +1,121 @@ diffusionRequest; if ($this->getRequest()->getStr('before')) { $results = array(); $is_file = true; } else { $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); $results = $browse_query->loadPaths(); $reason = $browse_query->getReasonForEmptyResultSet(); $is_file = ($reason == DiffusionBrowseQuery::REASON_IS_FILE); } $content = array(); if ($drequest->getTagContent()) { $title = 'Tag: '.$drequest->getSymbolicCommit(); $tag_view = new AphrontPanelView(); $tag_view->setHeader(phutil_escape_html($title)); $tag_view->appendChild( $this->markupText($drequest->getTagContent())); $content[] = $tag_view; } if (!$results) { if ($is_file) { $controller = new DiffusionBrowseFileController($this->getRequest()); $controller->setDiffusionRequest($drequest); $controller->setCurrentApplication($this->getCurrentApplication()); return $this->delegateToController($controller); } $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); $empty_result->setBrowseQuery($browse_query); $empty_result->setView($this->getRequest()->getStr('view')); $content[] = $empty_result; } else { $phids = array(); foreach ($results as $result) { $data = $result->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } } } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); $browse_table = new DiffusionBrowseTableView(); $browse_table->setDiffusionRequest($drequest); $browse_table->setHandles($handles); $browse_table->setPaths($results); $browse_table->setUser($this->getRequest()->getUser()); $browse_panel = new AphrontPanelView(); $browse_panel->appendChild($browse_table); $browse_panel->setNoBackground(); $content[] = $browse_panel; } $content[] = $this->buildOpenRevisions(); $readme_content = $browse_query->renderReadme($results); if ($readme_content) { $readme_panel = new AphrontPanelView(); $readme_panel->setHeader('README'); $readme_panel->appendChild($readme_content); $content[] = $readme_panel; } $nav = $this->buildSideNav('browse', false); $nav->appendChild($content); $crumbs = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'title' => array( nonempty(basename($drequest->getPath()), '/'), $drequest->getRepository()->getCallsign().' Repository', ), )); } private function markupText($text) { $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $text = $engine->markupText($text); - $text = phutil_render_tag( + $text = phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $text); return $text; } } diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php index c0835332f4..36bc8443a3 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php @@ -1,948 +1,945 @@ 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_tag( 'textarea', array( 'style' => $style, ), $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_tag( 'textarea', array( 'style' => $style, ), 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( + $corpus = phutil_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_tag( 'span', array( ), $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( + $blame[] = phutil_tag( 'th', array( 'class' => 'diffusion-blame-link', 'style' => 'background: '.$color, ), $before_link); - $blame[] = phutil_render_tag( + $blame[] = phutil_tag( 'th', array( 'class' => 'diffusion-rev-link', 'style' => 'background: '.$color, ), $commit_link); - $blame[] = phutil_render_tag( + $blame[] = phutil_tag( 'th', array( 'class' => 'diffusion-rev-link', 'style' => 'background: '.$color, ), $revision_link); - $blame[] = phutil_render_tag( + $blame[] = phutil_tag( 'th', array( 'class' => 'diffusion-author-link', 'style' => 'background: '.$color, ), idx($line, 'author')); } $line_link = phutil_tag( 'a', array( 'href' => $line_href, ), $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( + $blame[] = phutil_render_tag( 'td', array( ), $anchor_text. "\xE2\x80\x8B". // NOTE: See phabricator-oncopy behavior. $line['data']); - $rows[] = phutil_render_tag( + $rows[] = phutil_tag( 'tr', array( 'class' => ($line['highlighted'] ? 'highlighted' : null), ), - $blame. - $line_text); + $blame); $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/DiffusionHomeController.php b/src/applications/diffusion/controller/DiffusionHomeController.php index d06f0d9863..93d0330414 100644 --- a/src/applications/diffusion/controller/DiffusionHomeController.php +++ b/src/applications/diffusion/controller/DiffusionHomeController.php @@ -1,222 +1,222 @@ getRequest(); $user = $request->getUser(); $shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll(); if ($shortcuts) { $shortcuts = msort($shortcuts, 'getSequence'); $rows = array(); foreach ($shortcuts as $shortcut) { $rows[] = array( phutil_tag( 'a', array( 'href' => $shortcut->getHref(), ), $shortcut->getName()), phutil_escape_html($shortcut->getDescription()), ); } $shortcut_table = new AphrontTableView($rows); $shortcut_table->setHeaders( array( 'Link', '', )); $shortcut_table->setColumnClasses( array( 'pri', 'wide', )); $shortcut_panel = new AphrontPanelView(); $shortcut_panel->setHeader('Shortcuts'); $shortcut_panel->appendChild($shortcut_table); } else { $shortcut_panel = null; } $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->execute(); foreach ($repositories as $key => $repo) { if (!$repo->isTracked()) { unset($repositories[$key]); } } $repositories = msort($repositories, 'getName'); $repository_ids = mpull($repositories, 'getID'); $summaries = array(); $commits = array(); if ($repository_ids) { $summaries = queryfx_all( id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE repositoryID IN (%Ld)', PhabricatorRepository::TABLE_SUMMARY, $repository_ids); $summaries = ipull($summaries, null, 'repositoryID'); $commit_ids = array_filter(ipull($summaries, 'lastCommitID')); if ($commit_ids) { $commit = new PhabricatorRepositoryCommit(); $commits = $commit->loadAllWhere('id IN (%Ld)', $commit_ids); $commits = mpull($commits, null, 'getRepositoryID'); } } $branch = new PhabricatorRepositoryBranch(); $lint_messages = queryfx_all( $branch->establishConnection('r'), 'SELECT b.repositoryID, b.name, COUNT(lm.id) AS n FROM %T b LEFT JOIN %T lm ON b.id = lm.branchID GROUP BY b.id', $branch->getTableName(), PhabricatorRepository::TABLE_LINTMESSAGE); $lint_messages = igroup($lint_messages, 'repositoryID'); $rows = array(); $show_lint = false; foreach ($repositories as $repository) { $id = $repository->getID(); $commit = idx($commits, $id); $size = idx(idx($summaries, $id, array()), 'size', '-'); if ($size != '-') { $size = hsprintf( '%s', DiffusionRequest::generateDiffusionURI(array( 'callsign' => $repository->getCallsign(), 'action' => 'history', )), number_format($size)); } $lint_count = ''; $lint_branches = ipull(idx($lint_messages, $id, array()), 'n', 'name'); $branch = $repository->getDefaultArcanistBranch(); if (isset($lint_branches[$branch])) { $show_lint = true; - $lint_count = phutil_render_tag( + $lint_count = phutil_tag( 'a', array( 'href' => DiffusionRequest::generateDiffusionURI(array( 'callsign' => $repository->getCallsign(), 'action' => 'lint', )), ), number_format($lint_branches[$branch])); } $date = '-'; $time = '-'; if ($commit) { $date = phabricator_date($commit->getEpoch(), $user); $time = phabricator_time($commit->getEpoch(), $user); } $rows[] = array( phutil_tag( 'a', array( 'href' => '/diffusion/'.$repository->getCallsign().'/', ), $repository->getName()), phutil_escape_html($repository->getDetail('description')), PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()), $size, $lint_count, $commit ? DiffusionView::linkCommit( $repository, $commit->getCommitIdentifier()) : '-', $date, $time, ); } $repository_tool_uri = PhabricatorEnv::getProductionURI('/repository/'); $repository_tool = phutil_tag('a', array( 'href' => $repository_tool_uri, ), 'repository tool'); $no_repositories_txt = 'This instance of Phabricator does not have any '. 'configured repositories. '; if ($user->getIsAdmin()) { $no_repositories_txt .= 'To setup one or more repositories, visit the '. $repository_tool.'.'; } else { $no_repositories_txt .= 'Ask an administrator to setup one or more '. 'repositories via the '.$repository_tool.'.'; } $table = new AphrontTableView($rows); $table->setNoDataString($no_repositories_txt); $table->setHeaders( array( 'Repository', 'Description', 'VCS', 'Commits', 'Lint', 'Last', 'Date', 'Time', )); $table->setColumnClasses( array( 'pri', 'wide', '', 'n', 'n', 'n', '', 'right', )); $table->setColumnVisibility( array( true, true, true, true, $show_lint, true, true, true, )); $panel = new AphrontPanelView(); $panel->setHeader('Browse Repositories'); $panel->appendChild($table); $panel->setNoBackground(); $crumbs = $this->buildCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('All Repositories')) ->setHref($this->getApplicationURI())); return $this->buildStandardPageResponse( array( $crumbs, $shortcut_panel, $panel, ), array( 'title' => 'Diffusion', )); } } diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index 2aed5dbdd3..e603b1aade 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -1,289 +1,289 @@ paths = $paths; return $this; } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function renderLastModifiedColumns( array $handles, PhabricatorRepositoryCommit $commit = null, PhabricatorRepositoryCommitData $data = null) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $drequest = $this->getDiffusionRequest(); if ($commit) { $epoch = $commit->getEpoch(); $modified = DiffusionView::linkCommit( $drequest->getRepository(), $commit->getCommitIdentifier()); $date = phabricator_date($epoch, $this->user); $time = phabricator_time($epoch, $this->user); } else { $modified = ''; $date = ''; $time = ''; } if ($data) { $author_phid = $data->getCommitDetail('authorPHID'); if ($author_phid && isset($handles[$author_phid])) { $author = $handles[$author_phid]->renderLink(); } else { $author = self::renderName($data->getAuthorName()); } $committer = $data->getCommitDetail('committer'); if ($committer) { $committer_phid = $data->getCommitDetail('committerPHID'); if ($committer_phid && isset($handles[$committer_phid])) { $committer = $handles[$committer_phid]->renderLink(); } else { $committer = self::renderName($committer); } if ($author != $committer) { $author .= '/'.$committer; } } $details = AphrontTableView::renderSingleDisplayLine( phutil_escape_html($data->getSummary())); } else { $author = ''; $details = ''; } $return = array( 'commit' => $modified, 'date' => $date, 'time' => $time, 'author' => $author, 'details' => $details, ); $lint = self::loadLintMessagesCount($drequest); if ($lint !== null) { $return['lint'] = hsprintf( '%s', $drequest->generateURI(array( 'action' => 'lint', 'lint' => null, )), number_format($lint)); } return $return; } private static function loadLintMessagesCount(DiffusionRequest $drequest) { $branch = $drequest->loadBranch(); if (!$branch) { return null; } $conn = $drequest->getRepository()->establishConnection('r'); $where = ''; if ($drequest->getLint()) { $where = qsprintf( $conn, 'AND code = %s', $drequest->getLint()); } $like = (substr($drequest->getPath(), -1) == '/' ? 'LIKE %>' : '= %s'); return head(queryfx_one( $conn, 'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q AND path '.$like, PhabricatorRepository::TABLE_LINTMESSAGE, $branch->getID(), $where, '/'.$drequest->getPath())); } public function render() { $request = $this->getDiffusionRequest(); $repository = $request->getRepository(); $base_path = trim($request->getPath(), '/'); if ($base_path) { $base_path = $base_path.'/'; } $need_pull = array(); $rows = array(); $show_edit = false; foreach ($this->paths as $path) { $dir_slash = null; $file_type = $path->getFileType(); if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { $browse_text = $path->getPath().'/'; $dir_slash = '/'; $browse_link = ''.$this->linkBrowse( $base_path.$path->getPath().$dir_slash, array( - 'html' => $this->renderPathIcon( + 'text' => $this->renderPathIcon( 'dir', $browse_text), )).''; } else if ($file_type == DifferentialChangeType::FILE_SUBMODULE) { $browse_text = $path->getPath().'/'; $browse_link = ''. $this->linkExternal( $path->getHash(), $path->getExternalURI(), $this->renderPathIcon( 'ext', $browse_text)). ''; } else { if ($file_type == DifferentialChangeType::FILE_SYMLINK) { $type = 'link'; } else { $type = 'file'; } $browse_text = $path->getPath(); $browse_link = $this->linkBrowse( $base_path.$path->getPath(), array( - 'html' => $this->renderPathIcon($type, $browse_text), + 'text' => $this->renderPathIcon($type, $browse_text), )); } $commit = $path->getLastModifiedCommit(); if ($commit) { $drequest = clone $request; $drequest->setPath($request->getPath().$path->getPath().$dir_slash); $dict = $this->renderLastModifiedColumns( $this->handles, $commit, $path->getLastCommitData()); } else { $dict = array( 'lint' => celerity_generate_unique_node_id(), 'commit' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(), 'time' => celerity_generate_unique_node_id(), 'author' => celerity_generate_unique_node_id(), 'details' => celerity_generate_unique_node_id(), ); $uri = (string)$request->generateURI( array( 'action' => 'lastmodified', 'path' => $base_path.$path->getPath().$dir_slash, )); $need_pull[$uri] = $dict; foreach ($dict as $k => $uniq) { $dict[$k] = ''; } } $editor_button = ''; if ($this->user) { $editor_link = $this->user->loadEditorLink( $base_path.$path->getPath(), 1, $request->getRepository()->getCallsign()); if ($editor_link) { $show_edit = true; $editor_button = phutil_tag( 'a', array( 'href' => $editor_link, ), 'Edit'); } } $rows[] = array( $this->linkHistory($base_path.$path->getPath().$dir_slash), $editor_button, $browse_link, idx($dict, 'lint'), $dict['commit'], $dict['date'], $dict['time'], $dict['author'], $dict['details'], ); } if ($need_pull) { Javelin::initBehavior('diffusion-pull-lastmodified', $need_pull); } $branch = $this->getDiffusionRequest()->loadBranch(); $show_lint = ($branch && $branch->getLintCommit()); $lint = $request->getLint(); $view = new AphrontTableView($rows); $view->setHeaders( array( 'History', 'Edit', 'Path', ($lint ? phutil_escape_html($lint) : 'Lint'), 'Modified', 'Date', 'Time', 'Author/Committer', 'Details', )); $view->setColumnClasses( array( '', '', '', 'n', '', '', 'right', '', 'wide', )); $view->setColumnVisibility( array( true, $show_edit, true, $show_lint, true, true, true, true, true, )); return $view->render(); } private function renderPathIcon($type, $text) { require_celerity_resource('diffusion-icons-css'); return phutil_tag( 'span', array( 'class' => 'diffusion-path-icon diffusion-path-icon-'.$type, ), $text); } } diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index df5f139307..83dff57748 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -1,164 +1,162 @@ diffusionRequest = $request; return $this; } final public function getDiffusionRequest() { return $this->diffusionRequest; } final public function linkChange($change_type, $file_type, $path = null, $commit_identifier = null) { $text = DifferentialChangeType::getFullNameForChangeType($change_type); if ($change_type == DifferentialChangeType::TYPE_CHILD) { // TODO: Don't link COPY_AWAY without a direct change. return $text; } if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { return $text; } $href = $this->getDiffusionRequest()->generateURI( array( 'action' => 'change', 'path' => $path, 'commit' => $commit_identifier, )); - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => $href, ), $text); } final public function linkHistory($path) { $href = $this->getDiffusionRequest()->generateURI( array( 'action' => 'history', 'path' => $path, )); return phutil_tag( 'a', array( 'href' => $href, ), 'History'); } final public function linkBrowse($path, array $details = array()) { $href = $this->getDiffusionRequest()->generateURI( $details + array( 'action' => 'browse', 'path' => $path, )); - if (isset($details['html'])) { - $text = $details['html']; - } else if (isset($details['text'])) { - $text = phutil_escape_html($details['text']); + if (isset($details['text'])) { + $text = $details['text']; } else { $text = 'Browse'; } - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => $href, ), $text); } final public function linkExternal($hash, $uri, $text) { $href = id(new PhutilURI('/diffusion/external/')) ->setQueryParams( array( 'uri' => $uri, 'id' => $hash, )); - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => $href, ), $text); } final public static function nameCommit( PhabricatorRepository $repository, $commit) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $commit_name = substr($commit, 0, 12); break; default: $commit_name = $commit; break; } $callsign = $repository->getCallsign(); return "r{$callsign}{$commit_name}"; } final public static function linkCommit( PhabricatorRepository $repository, $commit) { $commit_name = self::nameCommit($repository, $commit); $callsign = $repository->getCallsign(); - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => "/r{$callsign}{$commit}", ), $commit_name); } final public static function linkRevision($id) { if (!$id) { return null; } - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => "/D{$id}", ), "D{$id}"); } final protected static function renderName($name) { $email = new PhutilEmailAddress($name); if ($email->getDisplayName() && $email->getDomainName()) { Javelin::initBehavior('phabricator-tooltips', array()); require_celerity_resource('aphront-tooltip-css'); return javelin_render_tag( 'span', array( 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $email->getAddress(), 'align' => 'E', 'size' => 'auto', ), ), phutil_escape_html($email->getDisplayName())); } return phutil_escape_html($name); } } diff --git a/src/applications/directory/controller/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/PhabricatorDirectoryMainController.php index 0302df17ec..b795ec238e 100644 --- a/src/applications/directory/controller/PhabricatorDirectoryMainController.php +++ b/src/applications/directory/controller/PhabricatorDirectoryMainController.php @@ -1,441 +1,442 @@ filter = idx($data, 'filter'); } public function processRequest() { $user = $this->getRequest()->getUser(); if ($this->filter == 'jump') { return $this->buildJumpResponse(); } $nav = $this->buildNav(); $project_query = new PhabricatorProjectQuery(); $project_query->setViewer($user); $project_query->withMemberPHIDs(array($user->getPHID())); $projects = $project_query->execute(); return $this->buildMainResponse($nav, $projects); } private function buildMainResponse($nav, array $projects) { assert_instances_of($projects, 'PhabricatorProject'); if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { $unbreak_panel = $this->buildUnbreakNowPanel(); $triage_panel = $this->buildNeedsTriagePanel($projects); $tasks_panel = $this->buildTasksPanel(); } else { $unbreak_panel = null; $triage_panel = null; $tasks_panel = null; } $jump_panel = $this->buildJumpPanel(); $revision_panel = $this->buildRevisionPanel(); $audit_panel = $this->buildAuditPanel(); $commit_panel = $this->buildCommitPanel(); $content = array( $jump_panel, $unbreak_panel, $triage_panel, $revision_panel, $tasks_panel, $audit_panel, $commit_panel, $this->minipanels, ); $nav->appendChild($content); $nav->appendChild(new PhabricatorGlobalUploadTargetView()); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Phabricator', )); } private function buildJumpResponse() { $request = $this->getRequest(); $jump = $request->getStr('jump'); $response = PhabricatorJumpNavHandler::jumpPostResponse($jump); if ($response) { return $response; } else if ($request->isFormPost()) { $query = new PhabricatorSearchQuery(); $query->setQuery($jump); $query->save(); return id(new AphrontRedirectResponse()) ->setURI('/search/'.$query->getQueryKey().'/'); } else { return id(new AphrontRedirectResponse())->setURI('/'); } } private function buildUnbreakNowPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $task_query = new ManiphestTaskQuery(); $task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN); $task_query->withPriority(ManiphestTaskPriority::PRIORITY_UNBREAK_NOW); $task_query->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( 'No "Unbreak Now!" Tasks', 'Nothing appears to be critically broken right now.'); } $panel = new AphrontPanelView(); $panel->setHeader('Unbreak Now!'); $panel->setCaption('Open tasks with "Unbreak Now!" priority.'); $panel->addButton( phutil_tag( 'a', array( 'href' => '/maniphest/view/all/', 'class' => 'grey button', ), "View All Unbreak Now \xC2\xBB")); $panel->appendChild($this->buildTaskListView($tasks)); $panel->setNoBackground(); return $panel; } private function buildNeedsTriagePanel(array $projects) { assert_instances_of($projects, 'PhabricatorProject'); $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); if ($projects) { $task_query = new ManiphestTaskQuery(); $task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN); $task_query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); $task_query->withAnyProjects(mpull($projects, 'getPHID')); $task_query->setLimit(10); $tasks = $task_query->execute(); } else { $tasks = array(); } if (!$tasks) { return $this->renderMiniPanel( 'No "Needs Triage" Tasks', 'No tasks in projects you are a member of '. 'need triage.'); } $panel = new AphrontPanelView(); $panel->setHeader('Needs Triage'); $panel->setCaption( 'Open tasks with "Needs Triage" priority in '. 'projects you are a member of.'); $panel->addButton( phutil_tag( 'a', array( // TODO: This should filter to just your projects' need-triage // tasks? 'href' => '/maniphest/view/projecttriage/', 'class' => 'grey button', ), "View All Triage \xC2\xBB")); $panel->appendChild($this->buildTaskListView($tasks)); $panel->setNoBackground(); return $panel; } private function buildRevisionPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $revision_query = new DifferentialRevisionQuery(); $revision_query->withStatus(DifferentialRevisionQuery::STATUS_OPEN); $revision_query->withResponsibleUsers(array($user_phid)); $revision_query->needRelationships(true); // NOTE: We need to unlimit this query to hit the responsible user // fast-path. $revision_query->setLimit(null); $revisions = $revision_query->execute(); list($blocking, $active, ) = DifferentialRevisionQuery::splitResponsible( $revisions, array($user_phid)); if (!$blocking && !$active) { return $this->renderMiniPanel( 'No Waiting Revisions', 'No revisions are waiting on you.'); } $panel = new AphrontPanelView(); $panel->setHeader('Revisions Waiting on You'); $panel->setCaption('Revisions waiting for you for review or commit.'); $panel->addButton( phutil_tag( 'a', array( 'href' => '/differential/', 'class' => 'button grey', ), "View Active Revisions \xC2\xBB")); $revision_view = id(new DifferentialRevisionListView()) ->setHighlightAge(true) ->setRevisions(array_merge($blocking, $active)) ->setFields(DifferentialRevisionListView::getDefaultFields()) ->setUser($user) ->loadAssets(); $phids = array_merge( array($user_phid), $revision_view->getRequiredHandlePHIDs()); $handles = $this->loadViewerHandles($phids); $revision_view->setHandles($handles); $panel->appendChild($revision_view); $panel->setNoBackground(); return $panel; } private function buildTasksPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $task_query = new ManiphestTaskQuery(); $task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN); $task_query->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY); $task_query->withOwners(array($user_phid)); $task_query->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( 'No Assigned Tasks', 'You have no assigned tasks.'); } $panel = new AphrontPanelView(); $panel->setHeader('Assigned Tasks'); $panel->addButton( phutil_tag( 'a', array( 'href' => '/maniphest/', 'class' => 'button grey', ), "View Active Tasks \xC2\xBB")); $panel->appendChild($this->buildTaskListView($tasks)); $panel->setNoBackground(); return $panel; } private function buildTaskListView(array $tasks) { assert_instances_of($tasks, 'ManiphestTask'); $user = $this->getRequest()->getUser(); $phids = array_merge( array_filter(mpull($tasks, 'getOwnerPHID')), array_mergev(mpull($tasks, 'getProjectPHIDs'))); $handles = $this->loadViewerHandles($phids); $view = new ManiphestTaskListView(); $view->setTasks($tasks); $view->setUser($user); $view->setHandles($handles); return $view; } private function buildJumpPanel($query=null) { $request = $this->getRequest(); $user = $request->getUser(); $uniq_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'phabricator-autofocus', array( 'id' => $uniq_id, )); require_celerity_resource('phabricator-jump-nav'); $doc_href = PhabricatorEnv::getDocLink('article/Jump_Nav_User_Guide.html'); $doc_link = phutil_tag( 'a', array( 'href' => $doc_href, ), 'Jump Nav User Guide'); $jump_input = phutil_tag( 'input', array( 'type' => 'text', 'class' => 'phabricator-jump-nav', 'name' => 'jump', 'id' => $uniq_id, 'value' => $query, )); - $jump_caption = phutil_render_tag( + $jump_caption = phutil_tag( 'p', array( 'class' => 'phabricator-jump-nav-caption', ), - 'Enter the name of an object like D123 to quickly jump to '. - 'it. See '.$doc_link.' or type help.'); + new PhutilSafeHTML( + 'Enter the name of an object like D123 to quickly jump to '. + 'it. See '.$doc_link.' or type help.')); $panel = new AphrontPanelView(); $panel->setHeader('Jump Nav'); $panel->appendChild( phabricator_render_form( $user, array( 'action' => '/jump/', 'method' => 'POST', 'class' => 'phabricator-jump-nav-form', ), $jump_input. $jump_caption)); return $panel; } private function renderMiniPanel($title, $body) { $panel = new AphrontMiniPanelView(); $panel->appendChild( phutil_render_tag( 'p', array( ), ''.$title.': '.$body)); $this->minipanels[] = $panel; } public function buildAuditPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = new PhabricatorAuditQuery(); $query->withAuditorPHIDs($phids); $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN); $query->withAwaitingUser($user); $query->needCommitData(true); $query->setLimit(10); $audits = $query->execute(); $commits = $query->getCommits(); if (!$audits) { return $this->renderMinipanel( 'No Audits', 'No commits are waiting for you to audit them.'); } $view = new PhabricatorAuditListView(); $view->setAudits($audits); $view->setCommits($commits); $view->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader('Audits'); $panel->setCaption('Commits awaiting your audit.'); $panel->appendChild($view); $panel->addButton( phutil_tag( 'a', array( 'href' => '/audit/', 'class' => 'button grey', ), "View Active Audits \xC2\xBB")); $panel->setNoBackground(); return $panel; } public function buildCommitPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = array($user->getPHID()); $query = new PhabricatorAuditCommitQuery(); $query->withAuthorPHIDs($phids); $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN); $query->needCommitData(true); $query->setLimit(10); $commits = $query->execute(); if (!$commits) { return $this->renderMinipanel( 'No Problem Commits', 'No one has raised concerns with your commits.'); } $view = new PhabricatorAuditCommitListView(); $view->setCommits($commits); $view->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader('Problem Commits'); $panel->setCaption('Commits which auditors have raised concerns about.'); $panel->appendChild($view); $panel->addButton( phutil_tag( 'a', array( 'href' => '/audit/', 'class' => 'button grey', ), "View Problem Commits \xC2\xBB")); $panel->setNoBackground(); return $panel; } } diff --git a/src/applications/feed/view/PhabricatorFeedStoryView.php b/src/applications/feed/view/PhabricatorFeedStoryView.php index 72b4a2a795..bf23a53890 100644 --- a/src/applications/feed/view/PhabricatorFeedStoryView.php +++ b/src/applications/feed/view/PhabricatorFeedStoryView.php @@ -1,125 +1,125 @@ title = $title; return $this; } public function setEpoch($epoch) { $this->epoch = $epoch; return $this; } public function setImage($image) { $this->image = $image; return $this; } public function setOneLineStory($one_line) { $this->oneLine = $one_line; return $this; } public function setViewed($viewed) { $this->viewed = $viewed; return $this; } public function getViewed() { return $this->viewed; } public function setHref($href) { $this->href = $href; return $this; } public function getHref() { return $this->href; } public function renderNotification() { $classes = array( 'phabricator-notification', ); if (!$this->viewed) { $classes[] = 'phabricator-notification-unread'; } return javelin_render_tag( 'div', array( 'class' => implode(' ', $classes), 'sigil' => 'notification', 'meta' => array( 'href' => $this->getHref(), ), ), $this->title); } public function render() { $head = phutil_render_tag( 'div', array( 'class' => 'phabricator-feed-story-head', ), nonempty($this->title, 'Untitled Story')); $body = null; $foot = null; $image_style = null; if (!$this->oneLine) { $body = phutil_render_tag( 'div', array( 'class' => 'phabricator-feed-story-body', ), $this->renderChildren()); if ($this->epoch) { $foot = phabricator_datetime($this->epoch, $this->user); } else { $foot = ''; } - $foot = phutil_render_tag( + $foot = phutil_tag( 'div', array( 'class' => 'phabricator-feed-story-foot', ), $foot); if ($this->image) { $image_style = 'background-image: url('.$this->image.')'; } } require_celerity_resource('phabricator-feed-css'); - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => $this->oneLine ? 'phabricator-feed-story phabricator-feed-story-one-line' : 'phabricator-feed-story', 'style' => $image_style, ), - $head.$body.$foot); + array($head, $body, $foot)); } } diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 26a095af51..cea2272bdb 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -1,164 +1,164 @@ phid = $data['phid']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $file = id(new PhabricatorFileQuery()) ->setViewer($user) ->withPHIDs(array($this->phid)) ->executeOne(); if (!$file) { return new Aphront404Response(); } $this->loadHandles(array($file->getAuthorPHID())); $phid = $file->getPHID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName('F'.$file->getID()) ->setHref($this->getApplicationURI("/info/{$phid}/"))); $header = id(new PhabricatorHeaderView()) ->setObjectName('F'.$file->getID()) ->setHeader($file->getName()); $actions = $this->buildActionView($file); $properties = $this->buildPropertyView($file); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, ), array( 'title' => $file->getName(), 'device' => true, )); } private function buildActionView(PhabricatorFile $file) { $request = $this->getRequest(); $user = $request->getUser(); $id = $file->getID(); $view = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($file); if ($file->isViewableInBrowser()) { $view->addAction( id(new PhabricatorActionView()) ->setName(pht('View File')) ->setIcon('preview') ->setHref($file->getViewURI())); } else { $view->addAction( id(new PhabricatorActionView()) ->setUser($user) ->setRenderAsForm(true) ->setName(pht('Download File')) ->setIcon('download') ->setHref($file->getViewURI())); } $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete File')) ->setIcon('delete') ->setHref($this->getApplicationURI("/delete/{$id}/")) ->setWorkflow(true)); return $view; } private function buildPropertyView(PhabricatorFile $file) { $request = $this->getRequest(); $user = $request->getUser(); $view = id(new PhabricatorPropertyListView()); if ($file->getAuthorPHID()) { $view->addProperty( pht('Author'), $this->getHandle($file->getAuthorPHID())->renderLink()); } $view->addProperty( pht('Created'), phabricator_datetime($file->getDateCreated(), $user)); $view->addProperty( pht('Size'), phabricator_format_bytes($file->getByteSize())); $view->addSectionHeader(pht('Technical Details')); $view->addProperty( pht('Mime Type'), phutil_escape_html($file->getMimeType())); $view->addProperty( pht('Engine'), phutil_escape_html($file->getStorageEngine())); $view->addProperty( pht('Format'), phutil_escape_html($file->getStorageFormat())); $view->addProperty( pht('Handle'), phutil_escape_html($file->getStorageHandle())); $metadata = $file->getMetadata(); if (!empty($metadata)) { $view->addSectionHeader(pht('Metadata')); foreach ($metadata as $key => $value) { $view->addProperty( PhabricatorFile::getMetadataName($key), phutil_escape_html($value)); } } if ($file->isViewableImage()) { // TODO: Clean this up after Pholio (dark backgrounds, standardization, // etc.) $image = phutil_tag( 'img', array( 'src' => $file->getViewURI(), 'class' => 'phabricator-property-list-image', )); - $linked_image = phutil_render_tag( + $linked_image = phutil_tag( 'a', array( 'href' => $file->getViewURI(), ), $image); $view->addTextContent($linked_image); } return $view; } } diff --git a/src/applications/macro/controller/PhabricatorMacroListController.php b/src/applications/macro/controller/PhabricatorMacroListController.php index 3756d346a0..4a7560ec89 100644 --- a/src/applications/macro/controller/PhabricatorMacroListController.php +++ b/src/applications/macro/controller/PhabricatorMacroListController.php @@ -1,165 +1,165 @@ getRequest(); $viewer = $request->getUser(); $macro_table = new PhabricatorFileImageMacro(); $file_table = new PhabricatorFile(); $conn = $macro_table->establishConnection('r'); $where = array(); $join = array(); $join[] = qsprintf($conn, '%T m', $macro_table->getTableName()); $filter = $request->getStr('name'); if (strlen($filter)) { $where[] = qsprintf($conn, 'm.name LIKE %~', $filter); } $authors = $request->getArr('authors'); if ($authors) { $join[] = qsprintf( $conn, '%T f ON m.filePHID = f.phid', $file_table->getTableName()); $where[] = qsprintf($conn, 'f.authorPHID IN (%Ls)', $authors); } $has_search = $where; if ($has_search) { $macros = queryfx_all( $conn, 'SELECT m.* FROM '.implode(' JOIN ', $join).' WHERE '.implode(' AND ', $where)); $macros = $macro_table->loadAllFromArray($macros); $nodata = pht('There are no macros matching the filter.'); } else { $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $macros = $macro_table->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize()); // Get an exact count since the size here is reasonably going to be a few // thousand at most in any reasonable case. $count = queryfx_one( $conn, 'SELECT COUNT(*) N FROM %T', $macro_table->getTableName()); $count = $count['N']; $pager->setCount($count); $pager->setURI($request->getRequestURI(), 'page'); $nodata = pht('There are no image macros yet.'); } if ($authors) { $author_phids = array_combine($authors, $authors); } else { $author_phids = array(); } $file_phids = mpull($macros, 'getFilePHID'); $files = array(); if ($file_phids) { $files = $file_table->loadAllWhere( "phid IN (%Ls)", $file_phids); $author_phids += mpull($files, 'getAuthorPHID', 'getAuthorPHID'); } $files_map = mpull($files, null, 'getPHID'); $this->loadHandles($author_phids); $author_handles = array_select_keys($this->getLoadedHandles(), $authors); $filter_form = id(new AphrontFormView()) ->setMethod('GET') ->setUser($request->getUser()) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($filter)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('authors') ->setLabel(pht('Authors')) ->setDatasource('/typeahead/common/users/') ->setValue(mpull($author_handles, 'getFullName'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Filter Image Macros'))); $filter_view = new AphrontListFilterView(); $filter_view->appendChild($filter_form); $nav = $this->buildSideNavView( $for_app = false, $has_search); $nav->selectFilter($has_search ? 'search' : '/'); $nav->appendChild($filter_view); $pinboard = new PhabricatorPinboardView(); $pinboard->setNoDataString($nodata); foreach ($macros as $macro) { $file_phid = $macro->getFilePHID(); $file = idx($files_map, $file_phid); $item = new PhabricatorPinboardItemView(); if ($file) { $item->setImageURI($file->getThumb220x165URI()); $item->setImageSize(220, 165); if ($file->getAuthorPHID()) { $author_handle = $this->getHandle($file->getAuthorPHID()); $item->appendChild( 'Created by '.$author_handle->renderLink()); } $datetime = phabricator_date($file->getDateCreated(), $viewer); $item->appendChild( - phutil_render_tag( + phutil_tag( 'div', array(), pht('Created on %s', $datetime))); } $item->setURI($this->getApplicationURI('/view/'.$macro->getID().'/')); $item->setHeader($macro->getName()); $pinboard->addItem($item); } $nav->appendChild($pinboard); if (!$has_search) { $nav->appendChild($pager); $name = pht('All Macros'); } else { $name = pht('Search'); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($name) ->setHref($request->getRequestURI())); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'device' => true, 'title' => pht('Image Macros'), )); } } diff --git a/src/applications/metamta/contentsource/PhabricatorContentSourceView.php b/src/applications/metamta/contentsource/PhabricatorContentSourceView.php index 5cd1278770..81ffda8de8 100644 --- a/src/applications/metamta/contentsource/PhabricatorContentSourceView.php +++ b/src/applications/metamta/contentsource/PhabricatorContentSourceView.php @@ -1,39 +1,39 @@ contentSource = $content_source; return $this; } public function render() { require_celerity_resource('phabricator-content-source-view-css'); $map = array( PhabricatorContentSource::SOURCE_WEB => 'Web', PhabricatorContentSource::SOURCE_CONDUIT => 'Conduit', PhabricatorContentSource::SOURCE_EMAIL => 'Email', PhabricatorContentSource::SOURCE_MOBILE => 'Mobile', PhabricatorContentSource::SOURCE_TABLET => 'Tablet', PhabricatorContentSource::SOURCE_FAX => 'Fax', ); $source = $this->contentSource->getSource(); $type = idx($map, $source, null); if (!$type) { return; } - return phutil_render_tag( + return phutil_tag( 'span', array( 'class' => "phabricator-content-source-view", ), "Via {$type}"); } } diff --git a/src/applications/oauthserver/controller/clientauthorization/PhabricatorOAuthClientAuthorizationListController.php b/src/applications/oauthserver/controller/clientauthorization/PhabricatorOAuthClientAuthorizationListController.php index 563a2816f4..c3b0a927af 100644 --- a/src/applications/oauthserver/controller/clientauthorization/PhabricatorOAuthClientAuthorizationListController.php +++ b/src/applications/oauthserver/controller/clientauthorization/PhabricatorOAuthClientAuthorizationListController.php @@ -1,158 +1,158 @@ getRequest(); $current_user = $request->getUser(); $offset = $request->getInt('offset', 0); $page_size = 100; $pager = new AphrontPagerView(); $request_uri = $request->getRequestURI(); $pager->setURI($request_uri, 'offset'); $pager->setPageSize($page_size); $pager->setOffset($offset); $query = new PhabricatorOAuthClientAuthorizationQuery(); $query->withUserPHIDs(array($current_user->getPHID())); $authorizations = $query->executeWithOffsetPager($pager); $client_authorizations = mpull($authorizations, null, 'getClientPHID'); $client_phids = array_keys($client_authorizations); if ($client_phids) { $clients = id(new PhabricatorOAuthServerClient()) ->loadAllWhere('phid in (%Ls)', $client_phids); } else { $clients = array(); } $client_dict = mpull($clients, null, 'getPHID'); $rows = array(); $rowc = array(); $highlight = $this->getHighlightPHIDs(); foreach ($client_authorizations as $client_phid => $authorization) { $client = $client_dict[$client_phid]; $created = phabricator_datetime($authorization->getDateCreated(), $current_user); $updated = phabricator_datetime($authorization->getDateModified(), $current_user); $scope_doc_href = PhabricatorEnv::getDoclink( 'article/Using_the_Phabricator_OAuth_Server.html#scopes' ); $row = array( phutil_tag( 'a', array( 'href' => $client->getViewURI(), ), $client->getName() ), - phutil_render_tag( + phutil_tag( 'a', array( 'href' => $scope_doc_href, ), $authorization->getScopeString() ), phabricator_datetime( $authorization->getDateCreated(), $current_user ), phabricator_datetime( $authorization->getDateModified(), $current_user ), phutil_tag( 'a', array( 'class' => 'small button grey', 'href' => $authorization->getEditURI(), ), 'Edit' ), ); $rows[] = $row; if (isset($highlight[$authorization->getPHID()])) { $rowc[] = 'highlighted'; } else { $rowc[] = ''; } } $panel = $this->buildClientAuthorizationList($rows, $rowc, $title); return $this->buildStandardPageResponse( array( $this->getNoticeView(), $panel->appendChild($pager), ), array('title' => $title) ); } private function buildClientAuthorizationList($rows, $rowc, $title) { $table = new AphrontTableView($rows); $table->setRowClasses($rowc); $table->setHeaders( array( 'Client', 'Scope', 'Created', 'Updated', '', )); $table->setColumnClasses( array( 'wide pri', '', '', '', 'action', )); if (empty($rows)) { $table->setNoDataString( 'You have not authorized any clients for this OAuthServer.' ); } $panel = new AphrontPanelView(); $panel->appendChild($table); $panel->setHeader($title); return $panel; } private function getNoticeView() { $edited = $this->getRequest()->getStr('edited'); $deleted = $this->getRequest()->getBool('deleted'); if ($edited) { $edited = phutil_escape_html($edited); $title = 'Successfully edited client authorization.'; } else if ($deleted) { $title = 'Successfully deleted client authorization.'; } else { $title = null; } if ($title) { $view = new AphrontErrorView(); $view->setTitle($title); $view->setSeverity(AphrontErrorView::SEVERITY_NOTICE); } else { $view = null; } return $view; } } diff --git a/src/applications/phame/view/PhamePostView.php b/src/applications/phame/view/PhamePostView.php index daae25dd53..582e32ccf7 100644 --- a/src/applications/phame/view/PhamePostView.php +++ b/src/applications/phame/view/PhamePostView.php @@ -1,238 +1,242 @@ skin = $skin; return $this; } public function getSkin() { return $this->skin; } public function setAuthor(PhabricatorObjectHandle $author) { $this->author = $author; return $this; } public function getAuthor() { return $this->author; } public function setPost(PhamePost $post) { $this->post = $post; return $this; } public function getPost() { return $this->post; } public function setBody($body) { $this->body = $body; return $this; } public function getBody() { return $this->body; } public function setSummary($summary) { $this->summary = $summary; return $this; } public function getSummary() { return $this->summary; } public function renderTitle() { $href = $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()); return phutil_render_tag( 'h2', array( 'class' => 'phame-post-title', ), phutil_tag( 'a', array( 'href' => $href, ), $this->getPost()->getTitle())); } public function renderDatePublished() { return phutil_tag( 'div', array( 'class' => 'phame-post-date', ), pht( 'Published on %s by %s', phabricator_datetime( $this->getPost()->getDatePublished(), $this->getUser()), $this->getAuthor()->getName())); } public function renderBody() { return phutil_render_tag( 'div', array( 'class' => 'phame-post-body', ), $this->getBody()); } public function renderSummary() { return phutil_render_tag( 'div', array( 'class' => 'phame-post-body', ), $this->getSummary()); } public function renderComments() { $post = $this->getPost(); switch ($post->getCommentsWidget()) { case 'facebook': $comments = $this->renderFacebookComments(); break; case 'disqus': $comments = $this->renderDisqusComments(); break; case 'none': default: $comments = null; break; } return $comments; } public function render() { - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'phame-post', ), - $this->renderTitle(). - $this->renderDatePublished(). - $this->renderBody(). - $this->renderComments()); + array( + $this->renderTitle(), + $this->renderDatePublished(), + $this->renderBody(), + $this->renderComments(), + )); } public function renderWithSummary() { - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'phame-post', ), - $this->renderTitle(). - $this->renderDatePublished(). - $this->renderSummary()); + array( + $this->renderTitle(), + $this->renderDatePublished(), + $this->renderSummary(), + )); } private function renderFacebookComments() { $fb_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); if (!$fb_id) { return null; } $fb_root = phutil_tag('div', array( 'id' => 'fb-root', ), '' ); $c_uri = '//connect.facebook.net/en_US/all.js#xfbml=1&appId='.$fb_id; $fb_js = jsprintf( '', $c_uri ); $uri = $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()); $fb_comments = phutil_tag('div', array( 'class' => 'fb-comments', 'data-href' => $uri, 'data-num-posts' => 5, ), '' ); return phutil_render_tag( 'div', array( 'class' => 'phame-comments-facebook', ), $fb_root. $fb_js. $fb_comments); } private function renderDisqusComments() { $disqus_shortname = PhabricatorEnv::getEnvConfig('disqus.shortname'); if (!$disqus_shortname) { return null; } $post = $this->getPost(); $disqus_thread = phutil_tag('div', array( 'id' => 'disqus_thread' ) ); // protip - try some var disqus_developer = 1; action to test locally $disqus_js = jsprintf( '', $post->getPHID(), $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()), $post->getTitle() ); return phutil_render_tag( 'div', array( 'class' => 'phame-comments-disqus', ), $disqus_thread. $disqus_js); } } diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index b24b00331b..1aa9625bf8 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -1,265 +1,265 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $document = id(new PhrictionDocument())->load($this->id); if (!$document) { return new Aphront404Response(); } $current = id(new PhrictionContent())->load($document->getContentID()); $l = $request->getInt('l'); $r = $request->getInt('r'); $ref = $request->getStr('ref'); if ($ref) { list($l, $r) = explode(',', $ref); } $content = id(new PhrictionContent())->loadAllWhere( 'documentID = %d AND version IN (%Ld)', $document->getID(), array($l, $r)); $content = mpull($content, null, 'getVersion'); $content_l = idx($content, $l, null); $content_r = idx($content, $r, null); if (!$content_l || !$content_r) { return new Aphront404Response(); } $text_l = $content_l->getContent(); $text_r = $content_r->getContent(); $text_l = wordwrap($text_l, 80); $text_r = wordwrap($text_r, 80); $engine = new PhabricatorDifferenceEngine(); $changeset = $engine->generateChangesetFromFileContent($text_l, $text_r); $changeset->setOldProperties( array( 'Title' => $content_l->getTitle(), )); $changeset->setNewProperties( array( 'Title' => $content_r->getTitle(), )); $whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL; $parser = new DifferentialChangesetParser(); $parser->setChangeset($changeset); $parser->setRenderingReference("{$l},{$r}"); $parser->setWhitespaceMode($whitespace_mode); $engine = new PhabricatorMarkupEngine(); $engine->setViewer($user); $engine->process(); $parser->setMarkupEngine($engine); $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $output = $parser->render($range_s, $range_e, $mask); if ($request->isAjax()) { return id(new PhabricatorChangesetResponse()) ->setRenderedChangeset($output); } require_celerity_resource('differential-changeset-view-css'); require_celerity_resource('syntax-highlighting-css'); require_celerity_resource('phriction-document-css'); Javelin::initBehavior('differential-show-more', array( 'uri' => '/phriction/diff/'.$document->getID().'/', 'whitespace' => $whitespace_mode, )); $slug = $document->getSlug(); $revert_l = $this->renderRevertButton($content_l, $current); $revert_r = $this->renderRevertButton($content_r, $current); $crumbs = new AphrontCrumbsView(); $crumbs->setCrumbs( array( 'Phriction', phutil_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($slug), ), $current->getTitle()), phutil_tag( 'a', array( 'href' => '/phriction/history/'.$document->getSlug().'/', ), 'History'), phutil_escape_html("Changes Between Version {$l} and Version {$r}"), )); $comparison_table = $this->renderComparisonTable( array( $content_r, $content_l, )); $navigation_table = null; if ($l + 1 == $r) { $nav_l = ($l > 1); $nav_r = ($r != $current->getVersion()); $uri = $request->getRequestURI(); if ($nav_l) { $link_l = phutil_tag( 'a', array( 'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1), ), "\xC2\xAB Previous Change"); } else { $link_l = 'Original Change'; } $link_r = null; if ($nav_r) { $link_r = phutil_tag( 'a', array( 'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1), ), "Next Change \xC2\xBB"); } else { $link_r = 'Most Recent Change'; } $navigation_table = '
    '; } $output = '
    '. $comparison_table->render(). '
    '. '
    '. $navigation_table. ''. ''. '
    '.$revert_l.''.$revert_r.'
    '. $output. '
    '; return $this->buildStandardPageResponse( array( $crumbs, $output, ), array( 'title' => 'Document History', )); } private function renderRevertButton( PhrictionContent $content, PhrictionContent $current) { $document_id = $content->getDocumentID(); $version = $content->getVersion(); if ($content->getChangeType() == PhrictionChangeType::CHANGE_DELETE) { // Don't show an edit/revert button for changes which deleted the content // since it's silly. return null; } if ($content->getID() == $current->getID()) { return phutil_tag( 'a', array( 'href' => '/phriction/edit/'.$document_id.'/', 'class' => 'button', ), 'Edit Current Version'); } - return phutil_render_tag( + return phutil_tag( 'a', array( 'href' => '/phriction/edit/'.$document_id.'/?revert='.$version, 'class' => 'button', ), - 'Revert to Version '.phutil_escape_html($version).'...'); + 'Revert to Version '.$version.'...'); } private function renderComparisonTable(array $content) { assert_instances_of($content, 'PhrictionContent'); $user = $this->getRequest()->getUser(); $phids = mpull($content, 'getAuthorPHID'); $handles = $this->loadViewerHandles($phids); $rows = array(); foreach ($content as $c) { $rows[] = array( phabricator_date($c->getDateCreated(), $user), phabricator_time($c->getDateCreated(), $user), phutil_escape_html('Version '.$c->getVersion()), $handles[$c->getAuthorPHID()]->renderLink(), phutil_escape_html($c->getDescription()), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Date', 'Time', 'Version', 'Author', 'Description', )); $table->setColumnClasses( array( '', 'right', 'pri', '', 'wide', )); return $table; } } diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php index 29f92e4b0d..0f4bec40f7 100644 --- a/src/applications/phriction/controller/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/PhrictionHistoryController.php @@ -1,151 +1,151 @@ slug = $data['slug']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $document = id(new PhrictionDocument())->loadOneWhere( 'slug = %s', PhabricatorSlug::normalize($this->slug)); if (!$document) { return new Aphront404Response(); } $current = id(new PhrictionContent())->load($document->getContentID()); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setURI($request->getRequestURI(), 'page'); $history = id(new PhrictionContent())->loadAllWhere( 'documentID = %d ORDER BY version DESC LIMIT %d, %d', $document->getID(), $pager->getOffset(), $pager->getPageSize() + 1); $history = $pager->sliceResults($history); $author_phids = mpull($history, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); $rows = array(); foreach ($history as $content) { $slug_uri = PhrictionDocument::getSlugURI($document->getSlug()); $version = $content->getVersion(); $diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); $vs_previous = 'Created'; if ($content->getVersion() != 1) { $uri = $diff_uri ->alter('l', $content->getVersion() - 1) ->alter('r', $content->getVersion()); $vs_previous = phutil_tag( 'a', array( 'href' => $uri, ), 'Show Change'); } $vs_head = 'Current'; if ($content->getID() != $document->getContentID()) { $uri = $diff_uri ->alter('l', $content->getVersion()) ->alter('r', $current->getVersion()); $vs_head = phutil_tag( 'a', array( 'href' => $uri, ), 'Show Later Changes'); } $change_type = PhrictionChangeType::getChangeTypeLabel( $content->getChangeType()); $rows[] = array( phabricator_date($content->getDateCreated(), $user), phabricator_time($content->getDateCreated(), $user), - phutil_render_tag( + phutil_tag( 'a', array( 'href' => $slug_uri.'?v='.$version, ), 'Version '.$version), $handles[$content->getAuthorPHID()]->renderLink(), $change_type, phutil_escape_html($content->getDescription()), $vs_previous, $vs_head, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Date', 'Time', 'Version', 'Author', 'Type', 'Description', 'Against Previous', 'Against Current', )); $table->setColumnClasses( array( '', 'right', 'pri', '', '', 'wide', '', '', )); $crumbs = $this->buildApplicationCrumbs(); $crumb_views = $this->renderBreadcrumbs($document->getSlug()); foreach ($crumb_views as $view) { $crumbs->addCrumb($view); } $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('History')) ->setHref( PhrictionDocument::getSlugURI($document->getSlug(), 'history'))); $panel = new AphrontPanelView(); $panel->setHeader('Document History'); $panel->appendChild($table); $panel->appendChild($pager); return $this->buildApplicationPage( array( $crumbs, $panel, ), array( 'title' => 'Document History', 'device' => true, )); } } diff --git a/src/applications/ponder/view/PonderCommentListView.php b/src/applications/ponder/view/PonderCommentListView.php index 566a975ef6..bef9942aa5 100644 --- a/src/applications/ponder/view/PonderCommentListView.php +++ b/src/applications/ponder/view/PonderCommentListView.php @@ -1,98 +1,98 @@ handles = $handles; return $this; } public function setComments(array $comments) { assert_instances_of($comments, 'PonderComment'); $this->comments = $comments; return $this; } public function setQuestionID($id) { $this->questionID = $id; return $this; } public function setActionURI($uri) { $this->actionURI = $uri; return $this; } public function setTarget($target) { $this->target = $target; return $this; } public function render() { require_celerity_resource('phabricator-remarkup-css'); require_celerity_resource('ponder-comment-table-css'); $user = $this->user; $handles = $this->handles; $comments = $this->comments; $comment_markup = array(); foreach ($comments as $comment) { $handle = $handles[$comment->getAuthorPHID()]; $body = PhabricatorMarkupEngine::renderOneObject( $comment, $comment->getMarkupField(), $this->user); $comment_anchor = ''; $comment_markup[] = ''. ''. $comment_anchor. ''. ''. '
    '. $body. ' —'. $handle->renderLink(). ' '. ''. phabricator_datetime($comment->getDateCreated(), $user). ''. '
    '. ''. ''; } $addview = id(new PonderAddCommentView) ->setTarget($this->target) ->setUser($user) ->setQuestionID($this->questionID) ->setActionURI($this->actionURI); $comment_markup[] = ''. ' '. ''.$addview->render().''. ''; - $comment_markup = phutil_render_tag( + $comment_markup = phutil_tag( 'table', array( 'class' => 'ponder-comments', ), - implode("\n", $comment_markup) + new PhutilSafeHTML(implode("\n", $comment_markup)) ); return $comment_markup; } } diff --git a/src/applications/ponder/view/PonderUserProfileView.php b/src/applications/ponder/view/PonderUserProfileView.php index 0bd15efcfc..a05796c0b8 100644 --- a/src/applications/ponder/view/PonderUserProfileView.php +++ b/src/applications/ponder/view/PonderUserProfileView.php @@ -1,100 +1,100 @@ questionoffset = $offset; return $this; } public function setAnswerOffset($offset) { $this->answeroffset = $offset; return $this; } public function setAnswers($data) { $this->answers = $data; return $this; } public function setPageSize($pagesize) { $this->pagesize = $pagesize; return $this; } public function setURI($uri, $aparam) { $this->uri = $uri; $this->aparam = $aparam; return $this; } public function render() { require_celerity_resource('ponder-core-view-css'); require_celerity_resource('ponder-feed-view-css'); $user = $this->user; $aoffset = $this->answeroffset; $answers = $this->answers; $uri = $this->uri; $aparam = $this->aparam; $pagesize = $this->pagesize; $apagebuttons = id(new AphrontPagerView()) ->setPageSize($pagesize) ->setOffset($aoffset) ->setURI( $uri ->setFragment('answers'), $aparam); $answers = $apagebuttons->sliceResults($answers); $view = new PhabricatorObjectItemListView(); $view->setUser($user); $view->setNoDataString(pht('No matching answers.')); foreach ($answers as $answer) { $question = $answer->getQuestion(); $author_phid = $question->getAuthorPHID(); $item = new PhabricatorObjectItemView(); $item->setObject($answer); $href = id(new PhutilURI('/Q' . $question->getID())) ->setFragment('A' . $answer->getID()); $item->setHeader( 'A'.$answer->getID().' '.self::abbreviate($answer->getContent()) ); $item->setHref($href); $item->addAttribute( pht('Created %s', phabricator_date($answer->getDateCreated(), $user))); $item->addAttribute(pht('%d Vote(s)', $answer->getVoteCount())); $item->addAttribute( pht( 'Answer to %s', - phutil_render_tag( + phutil_tag( 'a', array( 'href' => '/Q'.$question->getID(), ), phutil_escape_html(self::abbreviate($question->getTitle()))))); $view->addItem($item); } $view->appendChild($apagebuttons); return $view->render(); } private function abbreviate($w) { return phutil_utf8_shorten($w, 60); } } diff --git a/src/applications/search/view/PhabricatorSearchResultView.php b/src/applications/search/view/PhabricatorSearchResultView.php index 8e82d4ae03..b2a1aaafa9 100644 --- a/src/applications/search/view/PhabricatorSearchResultView.php +++ b/src/applications/search/view/PhabricatorSearchResultView.php @@ -1,118 +1,118 @@ handle = $handle; return $this; } public function setQuery(PhabricatorSearchQuery $query) { $this->query = $query; return $this; } public function setObject($object) { $this->object = $object; return $this; } public function render() { $handle = $this->handle; if (!$handle->isComplete()) { return; } $type_name = nonempty($handle->getTypeName(), 'Document'); require_celerity_resource('phabricator-search-results-css'); - $link = phutil_render_tag( + $link = phutil_tag( 'a', array( 'href' => $handle->getURI(), ), PhabricatorEnv::getProductionURI($handle->getURI())); $img = $handle->getImageURI(); if ($img) { $img = phutil_tag( 'div', array( 'class' => 'result-image', 'style' => "background-image: url('{$img}');", ), ''); } switch ($handle->getType()) { case PhabricatorPHIDConstants::PHID_TYPE_CMIT: $object_name = $handle->getName(); if ($this->object) { $data = $this->object->getCommitData(); $summary = $data->getSummary(); if (strlen($summary)) { $object_name = $handle->getName().': '.$data->getSummary(); } } break; default: $object_name = $handle->getFullName(); break; } return '
    '. $img. '
    '. - phutil_render_tag( + phutil_tag( 'a', array( 'class' => 'result-name', 'href' => $handle->getURI(), ), $this->emboldenQuery($object_name)). '
    '.$type_name.' · '.$link.'
    '. '
    '. '
    '. '
    '; } private function emboldenQuery($str) { if (!$this->query) { return phutil_escape_html($str); } $query = $this->query->getQuery(); $quoted_regexp = '/"([^"]*)"/'; $matches = array(1 => array()); preg_match_all($quoted_regexp, $query, $matches); $quoted_queries = $matches[1]; $query = preg_replace($quoted_regexp, '', $query); $query = preg_split('/\s+[+|]?/', $query); $query = array_filter($query); $query = array_merge($query, $quoted_queries); $str = phutil_escape_html($str); foreach ($query as $word) { $word = phutil_escape_html($word); $word = preg_quote($word, '/'); $word = preg_replace('/\\\\\*$/', '\w*', $word); $str = preg_replace( '/(?:^|\b)('.$word.')(?:\b|$)/i', '\1', $str); } - return $str; + return phutil_safe_html($str); } } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 18a76bee96..b1e15f43e1 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -1,456 +1,456 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $viewer_phid = $user->getPHID(); $poll = id(new PhabricatorSlowvotePoll())->load($this->id); if (!$poll) { return new Aphront404Response(); } $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( 'pollID = %d', $poll->getID()); $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( 'pollID = %d', $poll->getID()); $comments = id(new PhabricatorSlowvoteComment())->loadAllWhere( 'pollID = %d', $poll->getID()); $choices_by_option = mgroup($choices, 'getOptionID'); $comments_by_user = mpull($comments, null, 'getAuthorPHID'); $choices_by_user = mgroup($choices, 'getAuthorPHID'); $viewer_choices = idx($choices_by_user, $viewer_phid, array()); $viewer_comment = idx($comments_by_user, $viewer_phid, null); $comment_text = null; if ($viewer_comment) { $comment_text = $viewer_comment->getCommentText(); } if ($request->isFormPost()) { $comment = idx($comments_by_user, $viewer_phid, null); if ($comment) { $comment->delete(); } $comment_text = $request->getStr('comments'); if (strlen($comment_text)) { id(new PhabricatorSlowvoteComment()) ->setAuthorPHID($viewer_phid) ->setPollID($poll->getID()) ->setCommentText($comment_text) ->save(); } $votes = $request->getArr('vote'); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: // Enforce only one vote. $votes = array_slice($votes, 0, 1); break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // No filtering. break; default: throw new Exception("Unknown poll method!"); } foreach ($viewer_choices as $viewer_choice) { $viewer_choice->delete(); } foreach ($votes as $vote) { id(new PhabricatorSlowvoteChoice()) ->setAuthorPHID($viewer_phid) ->setPollID($poll->getID()) ->setOptionID($vote) ->save(); } return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID()); } require_celerity_resource('phabricator-slowvote-css'); $phids = array_merge( mpull($choices, 'getAuthorPHID'), mpull($comments, 'getAuthorPHID'), array( $poll->getAuthorPHID(), )); $query = new PhabricatorObjectHandleData($phids); $handles = $query->loadHandles(); $objects = $query->loadObjects(); if ($poll->getShuffle()) { shuffle($options); } $option_markup = array(); foreach ($options as $option) { $option_markup[] = $this->renderPollOption( $poll, $viewer_choices, $option); } $option_markup = implode("\n", $option_markup); $comments_by_option = array(); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: $choice_ids = array(); foreach ($choices_by_user as $user_phid => $user_choices) { $choice_ids[$user_phid] = head($user_choices)->getOptionID(); } foreach ($comments as $comment) { $choice = idx($choice_ids, $comment->getAuthorPHID()); if ($choice) { $comments_by_option[$choice][] = $comment; } } break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // All comments are grouped in approval voting. break; default: throw new Exception("Unknown poll method!"); } $result_markup = $this->renderResultMarkup( $poll, $options, $choices, $comments, $viewer_choices, $choices_by_option, $comments_by_option, $handles, $objects); if ($viewer_choices) { $instructions = 'Your vote has been recorded... but there is still ample time to '. 'rethink your position. Have you thoroughly considered all possible '. 'eventualities?'; } else { $instructions = 'This is a weighty matter indeed. Consider your choices with the '. 'greatest of care.'; } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( '

    '.$instructions.'

    ') ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Vote') ->setValue($option_markup)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Comments') ->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT) ->setName('comments') ->setValue($comment_text)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Cautiously Engage in Deliberations')); $panel = new AphrontPanelView(); $panel->setHeader(phutil_escape_html($poll->getQuestion())); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); $panel->appendChild('

    '); $panel->appendChild($result_markup); return $this->buildStandardPageResponse( $panel, array( 'title' => 'V'.$poll->getID().' '.$poll->getQuestion(), )); } private function renderComments(array $comments, array $handles) { assert_instances_of($comments, 'PhabricatorSlowvoteComment'); assert_instances_of($handles, 'PhabricatorObjectHandle'); $viewer = $this->getRequest()->getUser(); $engine = PhabricatorMarkupEngine::newSlowvoteMarkupEngine(); $comment_markup = array(); foreach ($comments as $comment) { $handle = $handles[$comment->getAuthorPHID()]; $markup = $engine->markupText($comment->getCommentText()); require_celerity_resource('phabricator-remarkup-css'); $comment_markup[] = ''. ''. $handle->renderLink(). '
    '. phabricator_datetime($comment->getDateCreated(), $viewer). '
    '. ''. '
    '. $markup. '
    '. ''. ''; } if ($comment_markup) { - $comment_markup = phutil_render_tag( + $comment_markup = phutil_tag( 'table', array( 'class' => 'phabricator-slowvote-comments', ), - implode("\n", $comment_markup)); + new PhutilSafeHTML(implode("\n", $comment_markup))); } else { $comment_markup = null; } return $comment_markup; } private function renderPollOption( PhabricatorSlowvotePoll $poll, array $viewer_choices, PhabricatorSlowvoteOption $option) { assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice'); $id = $option->getID(); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: // Render a radio button. $selected_option = head($viewer_choices); if ($selected_option) { $selected = $selected_option->getOptionID(); } else { $selected = null; } if ($selected == $id) { $checked = "checked"; } else { $checked = null; } $input = phutil_tag( 'input', array( 'type' => 'radio', 'name' => 'vote[]', 'value' => $id, 'checked' => $checked, )); break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // Render a check box. $checked = null; foreach ($viewer_choices as $choice) { if ($choice->getOptionID() == $id) { $checked = 'checked'; break; } } $input = phutil_tag( 'input', array( 'type' => 'checkbox', 'name' => 'vote[]', 'checked' => $checked, 'value' => $id, )); break; default: throw new Exception("Unknown poll method!"); } if ($checked) { $checked_class = 'phabricator-slowvote-checked'; } else { $checked_class = null; } - return phutil_render_tag( + return phutil_tag( 'label', array( 'class' => 'phabricator-slowvote-label '.$checked_class, ), - $input.phutil_escape_html($option->getName())); + array($input, $option->getName())); } private function renderVoteCount( PhabricatorSlowvotePoll $poll, array $choices, array $chosen) { assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); assert_instances_of($chosen, 'PhabricatorSlowvoteChoice'); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: $out_of_total = count($choices); break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // Count unique respondents for approval votes. $out_of_total = count(mpull($choices, null, 'getAuthorPHID')); break; default: throw new Exception("Unknown poll method!"); } return sprintf( '%d / %d (%d%%)', number_format(count($chosen)), number_format($out_of_total), $out_of_total ? round(100 * count($chosen) / $out_of_total) : 0); } private function renderResultMarkup( PhabricatorSlowvotePoll $poll, array $options, array $choices, array $comments, array $viewer_choices, array $choices_by_option, array $comments_by_option, array $handles, array $objects) { assert_instances_of($options, 'PhabricatorSlowvoteOption'); assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); assert_instances_of($comments, 'PhabricatorSlowvoteComment'); assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice'); assert_instances_of($handles, 'PhabricatorObjectHandle'); assert_instances_of($objects, 'PhabricatorLiskDAO'); $viewer_phid = $this->getRequest()->getUser()->getPHID(); $can_see_responses = false; $need_vote = false; switch ($poll->getResponseVisibility()) { case PhabricatorSlowvotePoll::RESPONSES_VISIBLE: $can_see_responses = true; break; case PhabricatorSlowvotePoll::RESPONSES_VOTERS: $can_see_responses = (bool)$viewer_choices; $need_vote = true; break; case PhabricatorSlowvotePoll::RESPONSES_OWNER: $can_see_responses = ($viewer_phid == $poll->getAuthorPHID()); break; } $result_markup = id(new AphrontFormLayoutView()) ->appendChild('

    Ongoing Deliberation

    '); if (!$can_see_responses) { if ($need_vote) { $reason = "You must vote to see the results."; } else { $reason = "The results are not public."; } $result_markup ->appendChild( '

    '.$reason.'

    '); return $result_markup; } foreach ($options as $option) { $id = $option->getID(); $chosen = idx($choices_by_option, $id, array()); $users = array_select_keys($handles, mpull($chosen, 'getAuthorPHID')); if ($users) { $user_markup = array(); foreach ($users as $handle) { $object = idx($objects, $handle->getPHID()); if (!$object) { continue; } $profile_image = $handle->getImageURI(); $user_markup[] = phutil_render_tag( 'a', array( 'href' => $handle->getURI(), 'class' => 'phabricator-slowvote-facepile', ), phutil_tag( 'img', array( 'src' => $profile_image, ))); } $user_markup = implode('', $user_markup); } else { $user_markup = 'This option has failed to appeal to anyone.'; } $comment_markup = $this->renderComments( idx($comments_by_option, $id, array()), $handles); $vote_count = $this->renderVoteCount( $poll, $choices, $chosen); $result_markup->appendChild( '
    '. '
    '. $vote_count. '
    '. '

    '.phutil_escape_html($option->getName()).'

    '. '
    '. $user_markup. '
    '. '
    '. $comment_markup. '
    '); } if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_APPROVAL && $comments) { $comment_markup = $this->renderComments( $comments, $handles); $result_markup->appendChild( '

    Motions Proposed for Consideration

    '); $result_markup->appendChild($comment_markup); } return $result_markup; } } diff --git a/src/applications/uiexample/examples/JavelinUIExample.php b/src/applications/uiexample/examples/JavelinUIExample.php index 9b86f3f9a6..5c9096d1e8 100644 --- a/src/applications/uiexample/examples/JavelinUIExample.php +++ b/src/applications/uiexample/examples/JavelinUIExample.php @@ -1,66 +1,66 @@ getRequest(); $user = $request->getUser(); // toggle-class $container_id = celerity_generate_unique_node_id(); $button_red_id = celerity_generate_unique_node_id(); $button_blue_id = celerity_generate_unique_node_id(); $button_red = javelin_render_tag( 'a', array( 'class' => 'button', 'sigil' => 'jx-toggle-class', 'href' => '#', 'id' => $button_red_id, 'meta' => array( 'map' => array( $container_id => 'jxui-red-border', $button_red_id => 'jxui-active', ), ), ), 'Toggle Red Border'); $button_blue = javelin_render_tag( 'a', array( 'class' => 'button jxui-active', 'sigil' => 'jx-toggle-class', 'href' => '#', 'id' => $button_blue_id, 'meta' => array( 'state' => true, 'map' => array( $container_id => 'jxui-blue-background', $button_blue_id => 'jxui-active', ), ), ), 'Toggle Blue Background'); - $div = phutil_render_tag( + $div = phutil_tag( 'div', array( 'id' => $container_id, 'class' => 'jxui-example-container jxui-blue-background', ), - $button_red.$button_blue); + array($button_red, $button_blue)); return array($div); } } diff --git a/src/applications/xhprof/view/PhabricatorXHProfSampleListView.php b/src/applications/xhprof/view/PhabricatorXHProfSampleListView.php index db08dfbbfc..7bc8117519 100644 --- a/src/applications/xhprof/view/PhabricatorXHProfSampleListView.php +++ b/src/applications/xhprof/view/PhabricatorXHProfSampleListView.php @@ -1,76 +1,76 @@ samples = $samples; return $this; } public function setShowType($show_type) { $this->showType = $show_type; } public function render() { $rows = array(); if (!$this->user) { throw new Exception("Call setUser() before rendering!"); } $user_phids = mpull($this->samples, 'getUserPHID'); $users = id(new PhabricatorObjectHandleData($user_phids))->loadObjects(); foreach ($this->samples as $sample) { - $sample_link = phutil_render_tag( + $sample_link = phutil_tag( 'a', array( 'href' => '/xhprof/profile/'.$sample->getFilePHID().'/', ), $sample->getFilePHID()); if ($this->showType) { if ($sample->getSampleRate() == 0) { $sample_link .= ' (manual run)'; } else { $sample_link .= ' (sampled)'; } } $rows[] = array( $sample_link, phabricator_datetime($sample->getDateCreated(), $this->user), number_format($sample->getUsTotal())." \xCE\xBCs", $sample->getHostname(), $sample->getRequestPath(), $sample->getController(), idx($users, $sample->getUserPHID()), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Sample', 'Date', 'Wall Time', 'Hostname', 'Request Path', 'Controller', 'User', )); $table->setColumnClasses( array( '', '', 'right', 'wide wrap', '', '', )); return $table->render(); } } diff --git a/src/infrastructure/diff/view/PhabricatorInlineSummaryView.php b/src/infrastructure/diff/view/PhabricatorInlineSummaryView.php index ba773721e1..f3aca0dc9f 100644 --- a/src/infrastructure/diff/view/PhabricatorInlineSummaryView.php +++ b/src/infrastructure/diff/view/PhabricatorInlineSummaryView.php @@ -1,111 +1,111 @@ groups[$name])) { $this->groups[$name] = $items; } else { $this->groups[$name] = array_merge($this->groups[$name], $items); } return $this; } public function render() { require_celerity_resource('inline-comment-summary-css'); return $this->renderHeader().$this->renderTable(); } private function renderHeader() { return phutil_tag( 'div', array( 'class' => 'phabricator-inline-summary', ), 'Inline Comments'); } private function renderTable() { $rows = array(); foreach ($this->groups as $group => $items) { $has_where = false; foreach ($items as $item) { if (!empty($item['where'])) { $has_where = true; break; } } $rows[] = ''. ''. phutil_escape_html($group). ''. ''; foreach ($items as $item) { $items = isort($items, 'line'); $line = $item['line']; $length = $item['length']; if ($length) { $lines = $line."\xE2\x80\x93".($line + $length); } else { $lines = $line; } if (isset($item['href'])) { $href = $item['href']; $target = '_blank'; $tail = " \xE2\x86\x97"; } else { $href = '#inline-'.$item['id']; $target = null; $tail = null; } $lines = phutil_escape_html($lines); if ($href) { $lines = phutil_render_tag( 'a', array( 'href' => $href, 'target' => $target, 'class' => 'num', ), $lines.$tail); } $where = idx($item, 'where'); $colspan = ($has_where ? '' : ' colspan="2"'); $rows[] = ''. ''.$lines.''. ($has_where ? ''. phutil_escape_html($where). '' : null). ''. '
    '. $item['content']. '
    '. ''. ''; } } - return phutil_render_tag( + return phutil_tag( 'table', array( 'class' => 'phabricator-inline-summary-table', ), - implode("\n", $rows)); + new PhutilSafeHTML(implode("\n", $rows))); } } diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php index 1c5f976ec4..b2219ad428 100644 --- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php +++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php @@ -1,72 +1,74 @@ load($matches[1]); if (!$countdown) { return $matches[0]; } $id = celerity_generate_unique_node_id(); $engine = $this->getEngine(); $token = $engine->storeText(''); $metadata_key = self::KEY_RULE_COUNTDOWN; $metadata = $engine->getTextMetadata($metadata_key, array()); $metadata[$id] = array($countdown->getDatepoint(), $token); $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_COUNTDOWN; $metadata = $engine->getTextMetadata($metadata_key, array()); if (!$metadata) { return; } require_celerity_resource('javelin-behavior-countdown-timer'); foreach ($metadata as $id => $info) { list($time, $token) = $info; - $count = phutil_render_tag( + $count = phutil_tag( 'span', array( 'id' => $id, ), - javelin_render_tag('span', - array('sigil' => 'phabricator-timer-days'), '').'d'. - javelin_render_tag('span', - array('sigil' => 'phabricator-timer-hours'), '').'h'. - javelin_render_tag('span', - array('sigil' => 'phabricator-timer-minutes'), '').'m'. - javelin_render_tag('span', - array('sigil' => 'phabricator-timer-seconds'), '').'s'); + array( + javelin_render_tag('span', + array('sigil' => 'phabricator-timer-days'), '').'d', + javelin_render_tag('span', + array('sigil' => 'phabricator-timer-hours'), '').'h', + javelin_render_tag('span', + array('sigil' => 'phabricator-timer-minutes'), '').'m', + javelin_render_tag('span', + array('sigil' => 'phabricator-timer-seconds'), '').'s', + )); Javelin::initBehavior('countdown-timer', array( 'timestamp' => $time, 'container' => $id, )); $engine->overwriteStoredText($token, $count); } $engine->setTextMetadata($metadata_key, array()); } } diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php index 72851c048d..6426f27fa0 100644 --- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php +++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php @@ -1,170 +1,170 @@ load($matches[1]); } if (!$file) { return $matches[0]; } $phid = $file->getPHID(); $engine = $this->getEngine(); $token = $engine->storeText(''); $metadata_key = self::KEY_RULE_EMBED_FILE; $metadata = $engine->getTextMetadata($metadata_key, array()); $bundle = array('token' => $token); $options = array( 'size' => 'thumb', 'layout' => 'left', 'float' => false, 'name' => null, ); if (!empty($matches[2])) { $matches[2] = trim($matches[2], ', '); $parser = new PhutilSimpleOptions(); $options = $parser->parse($matches[2]) + $options; } $file_name = coalesce($options['name'], $file->getName()); $options['name'] = $file_name; $attrs = array(); switch ((string)$options['size']) { case 'full': $attrs['src'] = $file->getBestURI(); $options['image_class'] = null; break; case 'thumb': default: $attrs['src'] = $file->getPreview220URI(); $options['image_class'] = 'phabricator-remarkup-embed-image'; break; } $bundle['attrs'] = $attrs; $bundle['options'] = $options; $bundle['meta'] = array( 'phid' => $file->getPHID(), 'viewable' => $file->isViewableImage(), 'uri' => $file->getBestURI(), 'dUri' => $file->getDownloadURI(), 'name' => $options['name'], ); $metadata[$phid][] = $bundle; $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_EMBED_FILE; $metadata = $engine->getTextMetadata($metadata_key, array()); if (!$metadata) { return; } $file_phids = array(); foreach ($metadata as $phid => $bundles) { foreach ($bundles as $data) { $options = $data['options']; $meta = $data['meta']; if (!$meta['viewable'] || $options['layout'] == 'link') { $link = id(new PhabricatorFileLinkView()) ->setFilePHID($meta['phid']) ->setFileName($meta['name']) ->setFileDownloadURI($meta['dUri']) ->setFileViewURI($meta['uri']) ->setFileViewable($meta['viewable']); $embed = $link->render(); $engine->overwriteStoredText($data['token'], $embed); continue; } require_celerity_resource('lightbox-attachment-css'); $img = phutil_tag('img', $data['attrs']); $embed = javelin_render_tag( 'a', array( 'href' => $meta['uri'], 'class' => $options['image_class'], 'sigil' => 'lightboxable', 'mustcapture' => true, 'meta' => $meta, ), $img); $layout_class = null; switch ($options['layout']) { case 'right': case 'center': case 'inline': case 'left': $layout_class = 'phabricator-remarkup-embed-layout-'. $options['layout']; break; default: $layout_class = 'phabricator-remarkup-embed-layout-left'; break; } if ($options['float']) { switch ($options['layout']) { case 'center': case 'inline': break; case 'right': $layout_class .= ' phabricator-remarkup-embed-float-right'; break; case 'left': default: $layout_class .= ' phabricator-remarkup-embed-float-left'; break; } } if ($layout_class) { - $embed = phutil_render_tag( + $embed = phutil_tag( 'div', array( 'class' => $layout_class, ), $embed); } $engine->overwriteStoredText($data['token'], $embed); } $file_phids[] = $phid; } $engine->setTextMetadata(self::KEY_EMBED_FILE_PHIDS, $file_phids); $engine->setTextMetadata($metadata_key, array()); } } diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php index 5d11ae0131..b81c37cc2f 100644 --- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php +++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php @@ -1,51 +1,51 @@ getObjectNamePrefix(); $id = $this->getObjectIDPattern(); return preg_replace_callback( "@\b({$prefix})({$id})(?:#([-\w\d]+))?\b@", array($this, 'markupObjectNameLink'), $text); } public function markupObjectNameLink($matches) { list(, $prefix, $id) = $matches; if (isset($matches[3])) { $href = $matches[3]; $text = $matches[3]; if (preg_match('@^(?:comment-)?(\d{1,7})$@', $href, $matches)) { // Maximum length is 7 because 12345678 could be a file hash. $href = "comment-{$matches[1]}"; $text = $matches[1]; } $href = "/{$prefix}{$id}#{$href}"; $text = "{$prefix}{$id}#{$text}"; } else { $href = "/{$prefix}{$id}"; $text = "{$prefix}{$id}"; } return $this->getEngine()->storeText( - phutil_render_tag( + phutil_tag( 'a', array( 'href' => $href, ), $text)); } } diff --git a/src/view/control/AphrontPagerView.php b/src/view/control/AphrontPagerView.php index a0d98d8dbe..00a405273d 100644 --- a/src/view/control/AphrontPagerView.php +++ b/src/view/control/AphrontPagerView.php @@ -1,218 +1,218 @@ pageSize = max(1, $page_size); return $this; } final public function setOffset($offset) { $this->offset = max(0, $offset); return $this; } final public function getOffset() { return $this->offset; } final public function getPageSize() { return $this->pageSize; } final public function setCount($count) { $this->count = $count; return $this; } final public function setHasMorePages($has_more) { $this->hasMorePages = $has_more; return $this; } final public function setURI(PhutilURI $uri, $paging_parameter) { $this->uri = $uri; $this->pagingParameter = $paging_parameter; return $this; } final public function setSurroundingPages($pages) { $this->surroundingPages = max(0, $pages); return $this; } private function computeCount() { if ($this->count !== null) { return $this->count; } return $this->getOffset() + $this->getPageSize() + ($this->hasMorePages ? 1 : 0); } private function isExactCountKnown() { return $this->count !== null; } /** * A common paging strategy is to select one extra record and use that to * indicate that there's an additional page (this doesn't give you a * complete page count but is often faster than counting the total number * of items). This method will take a result array, slice it down to the * page size if necessary, and call setHasMorePages() if there are more than * one page of results. * * $results = queryfx_all( * $conn, * 'SELECT ... LIMIT %d, %d', * $pager->getOffset(), * $pager->getPageSize() + 1); * $results = $pager->sliceResults($results); * * @param list Result array. * @return list One page of results. */ public function sliceResults(array $results) { if (count($results) > $this->getPageSize()) { $results = array_slice($results, 0, $this->getPageSize(), true); $this->setHasMorePages(true); } return $results; } public function setEnableKeyboardShortcuts($enable) { $this->enableKeyboardShortcuts = $enable; return $this; } public function render() { if (!$this->uri) { throw new Exception( "You must call setURI() before you can call render()."); } require_celerity_resource('aphront-pager-view-css'); $page = (int)floor($this->getOffset() / $this->getPageSize()); $last = ((int)ceil($this->computeCount() / $this->getPageSize())) - 1; $near = $this->surroundingPages; $min = $page - $near; $max = $page + $near; // Limit the window size to no larger than the number of available pages. if ($max - $min > $last) { $max = $min + $last; if ($max == $min) { return '
    '; } } // Slide the window so it is entirely over displayable pages. if ($min < 0) { $max += 0 - $min; $min += 0 - $min; } if ($max > $last) { $min -= $max - $last; $max -= $max - $last; } // Build up a list of tuples which describe the // links we'll display, then render them all at once. $links = array(); $prev_index = null; $next_index = null; if ($min > 0) { $links[] = array(0, 'First', null); } if ($page > 0) { $links[] = array($page - 1, 'Prev', null); $prev_index = $page - 1; } for ($ii = $min; $ii <= $max; $ii++) { $links[] = array($ii, $ii + 1, ($ii == $page) ? 'current' : null); } if ($page < $last && $last > 0) { $links[] = array($page + 1, 'Next', null); $next_index = $page + 1; } if ($max < ($last - 1)) { $links[] = array($last, 'Last', null); } $base_uri = $this->uri; $parameter = $this->pagingParameter; if ($this->enableKeyboardShortcuts) { $pager_links = array(); $pager_index = array( 'prev' => $prev_index, 'next' => $next_index, ); foreach ($pager_index as $key => $index) { if ($index !== null) { $display_index = $this->getDisplayIndex($index); $pager_links[$key] = (string)$base_uri->alter( $parameter, $display_index); } } Javelin::initBehavior('phabricator-keyboard-pager', $pager_links); } // Convert tuples into rendered nodes. $rendered_links = array(); foreach ($links as $link) { list($index, $label, $class) = $link; $display_index = $this->getDisplayIndex($index); $link = $base_uri->alter($parameter, $display_index); - $rendered_links[] = phutil_render_tag( + $rendered_links[] = phutil_tag( 'a', array( 'href' => $link, 'class' => $class, ), $label); } return '
    '. implode('', $rendered_links). '
    '; } private function getDisplayIndex($page_index) { $page_size = $this->getPageSize(); // Use a 1-based sequence for display so that the number in the URI is // the same as the page number you're on. if ($page_index == 0) { // No need for the first page to say page=1. $display_index = null; } else { $display_index = $page_index * $page_size; } return $display_index; } } diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index d723907554..92be011692 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -1,320 +1,320 @@ data = $data; } public function setHeaders(array $headers) { $this->headers = $headers; return $this; } public function setColumnClasses(array $column_classes) { $this->columnClasses = $column_classes; return $this; } public function setRowClasses(array $row_classes) { $this->rowClasses = $row_classes; return $this; } public function setCellClasses(array $cell_classes) { $this->cellClasses = $cell_classes; return $this; } public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; return $this; } public function setClassName($class_name) { $this->className = $class_name; return $this; } public function setZebraStripes($zebra_stripes) { $this->zebraStripes = $zebra_stripes; return $this; } public function setColumnVisibility(array $visibility) { $this->columnVisibility = $visibility; return $this; } public function setDeviceVisibility(array $device_visibility) { $this->deviceVisibility = $device_visibility; return $this; } public function setDeviceReadyTable($ready) { $this->deviceReadyTable = $ready; return $this; } public function setShortHeaders(array $short_headers) { $this->shortHeaders = $short_headers; return $this; } /** * Parse a sorting parameter: * * list($sort, $reverse) = AphrontTableView::parseSortParam($sort_param); * * @param string Sort request parameter. * @return pair Sort value, sort direction. */ public static function parseSort($sort) { return array(ltrim($sort, '-'), preg_match('/^-/', $sort)); } public function makeSortable( PhutilURI $base_uri, $param, $selected, $reverse, array $sort_values) { $this->sortURI = $base_uri; $this->sortParam = $param; $this->sortSelected = $selected; $this->sortReverse = $reverse; $this->sortValues = array_values($sort_values); return $this; } public function render() { require_celerity_resource('aphront-table-view-css'); $table_class = $this->className; if ($this->deviceReadyTable) { $table_class .= ' aphront-table-view-device-ready'; } if ($table_class !== null) { $table_class = ' class="aphront-table-view '.$table_class.'"'; } else { $table_class = ' class="aphront-table-view"'; } $table = array(''); $col_classes = array(); foreach ($this->columnClasses as $key => $class) { if (strlen($class)) { $col_classes[] = $class; } else { $col_classes[] = null; } } $visibility = array_values($this->columnVisibility); $device_visibility = array_values($this->deviceVisibility); $headers = $this->headers; $short_headers = $this->shortHeaders; $sort_values = $this->sortValues; if ($headers) { while (count($headers) > count($visibility)) { $visibility[] = true; } while (count($headers) > count($device_visibility)) { $device_visibility[] = true; } while (count($headers) > count($short_headers)) { $short_headers[] = null; } while (count($headers) > count($sort_values)) { $sort_values[] = null; } $table[] = ''; foreach ($headers as $col_num => $header) { if (!$visibility[$col_num]) { continue; } $classes = array(); if (!empty($col_classes[$col_num])) { $classes[] = $col_classes[$col_num]; } if (empty($device_visiblity[$col_num])) { $classes[] = 'aphront-table-nodevice'; } if ($sort_values[$col_num] !== null) { $classes[] = 'aphront-table-view-sortable'; $sort_value = $sort_values[$col_num]; $sort_glyph_class = 'aphront-table-down-sort'; if ($sort_value == $this->sortSelected) { if ($this->sortReverse) { $sort_glyph_class = 'aphront-table-up-sort'; } else if (!$this->sortReverse) { $sort_value = '-'.$sort_value; } $classes[] = 'aphront-table-view-sortable-selected'; } $sort_glyph = phutil_tag( 'span', array( 'class' => $sort_glyph_class, ), ''); $header = phutil_render_tag( 'a', array( 'href' => $this->sortURI->alter($this->sortParam, $sort_value), 'class' => 'aphront-table-view-sort-link', ), $header.' '.$sort_glyph); } if ($classes) { $class = ' class="'.implode(' ', $classes).'"'; } else { $class = null; } if ($short_headers[$col_num] !== null) { $header_nodevice = phutil_render_tag( 'span', array( 'class' => 'aphront-table-view-nodevice', ), $header); $header_device = phutil_tag( 'span', array( 'class' => 'aphront-table-view-device', ), $short_headers[$col_num]); $header = $header_nodevice.$header_device; } $table[] = ''.$header.''; } $table[] = ''; } foreach ($col_classes as $key => $value) { if (($sort_values[$key] !== null) && ($sort_values[$key] == $this->sortSelected)) { $value = trim($value.' sorted-column'); } if ($value !== null) { $col_classes[$key] = $value; } } $data = $this->data; if ($data) { $row_num = 0; foreach ($data as $row) { while (count($row) > count($col_classes)) { $col_classes[] = null; } while (count($row) > count($visibility)) { $visibility[] = true; } $class = idx($this->rowClasses, $row_num); if ($this->zebraStripes && ($row_num % 2)) { if ($class !== null) { $class = 'alt alt-'.$class; } else { $class = 'alt'; } } if ($class !== null) { $class = ' class="'.$class.'"'; } $table[] = ''; // NOTE: Use of a separate column counter is to allow this to work // correctly if the row data has string or non-sequential keys. $col_num = 0; foreach ($row as $value) { if (!$visibility[$col_num]) { ++$col_num; continue; } $class = $col_classes[$col_num]; if (!empty($this->cellClasses[$row_num][$col_num])) { $class = trim($class.' '.$this->cellClasses[$row_num][$col_num]); } if ($class !== null) { $table[] = ''; } else { $table[] = ''; } $table[] = $value.''; ++$col_num; } ++$row_num; } } else { $colspan = max(count(array_filter($visibility)), 1); $table[] = ''. coalesce($this->noDataString, 'No data available.'). ''; } $table[] = ''; $html = implode('', $table); return '
    '.$html.'
    '; } public static function renderSingleDisplayLine($line) { // TODO: Is there a cleaner way to do this? We use a relative div with // overflow hidden to provide the bounds, and an absolute span with // white-space: pre to prevent wrapping. We need to append a character // (  -- nonbreaking space) afterward to give the bounds div height // (alternatively, we could hard-code the line height). This is gross but // it's not clear that there's a better appraoch. - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'single-display-line-bounds', ), phutil_render_tag( 'span', array( 'class' => 'single-display-line-content', ), $line).' '); } } diff --git a/src/view/control/AphrontTokenizerTemplateView.php b/src/view/control/AphrontTokenizerTemplateView.php index 0eda5ec879..178b313873 100644 --- a/src/view/control/AphrontTokenizerTemplateView.php +++ b/src/view/control/AphrontTokenizerTemplateView.php @@ -1,88 +1,92 @@ id = $id; return $this; } public function setValue(array $value) { $this->value = $value; return $this; } public function getValue() { return $this->value; } public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } public function render() { require_celerity_resource('aphront-tokenizer-control-css'); $id = $this->id; $name = $this->getName(); $values = nonempty($this->getValue(), array()); $tokens = array(); foreach ($values as $key => $value) { $tokens[] = $this->renderToken($key, $value); } $input = javelin_render_tag( 'input', array( 'mustcapture' => true, 'name' => $name, 'class' => 'jx-tokenizer-input', 'sigil' => 'tokenizer-input', 'style' => 'width: 0px;', 'disabled' => 'disabled', 'type' => 'text', )); - return phutil_render_tag( + $content = $tokens; + $content[] = $input; + $content[] = phutil_tag('div', array('style' => 'clear: both;'), ''); + + return phutil_tag( 'div', array( 'id' => $id, 'class' => 'jx-tokenizer-container', ), - implode('', $tokens). - $input. - '
    '); + $content); } private function renderToken($key, $value) { $input_name = $this->getName(); if ($input_name) { $input_name .= '[]'; } - return phutil_render_tag( + return phutil_tag( 'a', array( 'class' => 'jx-tokenizer-token', ), - phutil_escape_html($value). - phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => $input_name, - 'value' => $key, - )). - ''); + array( + $value, + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $input_name, + 'value' => $key, + )). + phutil_tag('span', array('class' => 'jx-tokenizer-x-placeholder'), ''), + )); } } diff --git a/src/view/form/AphrontErrorView.php b/src/view/form/AphrontErrorView.php index d3dc20a0a3..0ff078261b 100644 --- a/src/view/form/AphrontErrorView.php +++ b/src/view/form/AphrontErrorView.php @@ -1,91 +1,93 @@ title = $title; return $this; } public function setSeverity($severity) { $this->severity = $severity; return $this; } public function setErrors(array $errors) { $this->errors = $errors; return $this; } public function setID($id) { $this->id = $id; return $this; } final public function render() { require_celerity_resource('aphront-error-view-css'); $errors = $this->errors; if ($errors) { $list = array(); foreach ($errors as $error) { $list[] = phutil_tag( 'li', array(), $error); } - $list = phutil_render_tag( + $list = phutil_tag( 'ul', array( 'class' => 'aphront-error-view-list', ), - implode("\n", $list)); + $list); } else { $list = null; } $title = $this->title; if (strlen($title)) { $title = phutil_tag( 'h1', array( 'class' => 'aphront-error-view-head', ), $title); } else { $title = null; } $this->severity = nonempty($this->severity, self::SEVERITY_ERROR); $more_classes = array(); $more_classes[] = 'aphront-error-severity-'.$this->severity; $more_classes = implode(' ', $more_classes); return phutil_render_tag( 'div', array( 'id' => $this->id, 'class' => 'aphront-error-view '.$more_classes, ), - $title. - phutil_render_tag( - 'div', - array( - 'class' => 'aphront-error-view-body', - ), - $this->renderChildren(). - $list)); + array( + $title, + phutil_render_tag( + 'div', + array( + 'class' => 'aphront-error-view-body', + ), + $this->renderChildren(). + $list), + )); } } diff --git a/src/view/form/control/AphrontFormSelectControl.php b/src/view/form/control/AphrontFormSelectControl.php index bc01e742b3..9896eedef5 100644 --- a/src/view/form/control/AphrontFormSelectControl.php +++ b/src/view/form/control/AphrontFormSelectControl.php @@ -1,67 +1,67 @@ options = $options; return $this; } public function getOptions() { return $this->options; } protected function renderInput() { return self::renderSelectTag( $this->getValue(), $this->getOptions(), array( 'name' => $this->getName(), 'disabled' => $this->getDisabled() ? 'disabled' : null, 'id' => $this->getID(), )); } public static function renderSelectTag( $selected, array $options, array $attrs = array()) { $option_tags = self::renderOptions($selected, $options); return javelin_render_tag( 'select', $attrs, implode("\n", $option_tags)); } private static function renderOptions($selected, array $options) { $tags = array(); foreach ($options as $value => $thing) { if (is_array($thing)) { - $tags[] = phutil_render_tag( + $tags[] = phutil_tag( 'optgroup', array( 'label' => $value, ), - implode("\n", self::renderOptions($selected, $thing))); + self::renderOptions($selected, $thing)); } else { $tags[] = phutil_tag( 'option', array( 'selected' => ($value == $selected) ? 'selected' : null, 'value' => $value, ), $thing); } } return $tags; } } diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 6b601ff735..1272007c0c 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -1,154 +1,154 @@ getID(); if (!$id) { $id = celerity_generate_unique_node_id(); $this->setID($id); } // We need to have this if previews render images, since Ajax can not // currently ship JS or CSS. require_celerity_resource('lightbox-attachment-css'); Javelin::initBehavior( 'aphront-drag-and-drop-textarea', array( 'target' => $id, 'activatedClass' => 'aphront-textarea-drag-and-drop', 'uri' => '/file/dropupload/', )); Javelin::initBehavior('phabricator-remarkup-assist', array()); Javelin::initBehavior('phabricator-tooltips', array()); $actions = array( 'b' => array( 'tip' => pht('Bold'), ), 'i' => array( 'tip' => pht('Italics'), ), 'tt' => array( 'tip' => pht('Monospaced'), ), array( 'spacer' => true, ), 'ul' => array( 'tip' => pht('Bulleted List'), ), 'ol' => array( 'tip' => pht('Numbered List'), ), 'code' => array( 'tip' => pht('Code Block'), ), 'table' => array( 'tip' => pht('Table'), ), array( 'spacer' => true, ), 'meme' => array( 'tip' => pht('Meme'), ), 'help' => array( 'tip' => pht('Help'), 'align' => 'right', 'href' => PhabricatorEnv::getDoclink( 'article/Remarkup_Reference.html'), ), ); $buttons = array(); foreach ($actions as $action => $spec) { if (idx($spec, 'spacer')) { $buttons[] = phutil_tag( 'span', array( 'class' => 'remarkup-assist-separator', ), ''); continue; } $classes = array(); $classes[] = 'remarkup-assist-button'; if (idx($spec, 'align') == 'right') { $classes[] = 'remarkup-assist-right'; } $href = idx($spec, 'href', '#'); if ($href == '#') { $meta = array('action' => $action); $mustcapture = true; $target = null; } else { $meta = array(); $mustcapture = null; $target = '_blank'; } $tip = idx($spec, 'tip'); if ($tip) { $meta['tip'] = $tip; } require_celerity_resource('sprite-icon-css'); $buttons[] = javelin_render_tag( 'a', array( 'class' => implode(' ', $classes), 'href' => $href, 'sigil' => 'remarkup-assist has-tooltip', 'meta' => $meta, 'mustcapture' => $mustcapture, 'target' => $target, 'tabindex' => -1, ), phutil_tag( 'div', array( 'class' => 'remarkup-assist sprite-icon remarkup-assist-'.$action, ), '')); } - $buttons = phutil_render_tag( + $buttons = phutil_tag( 'div', array( 'class' => 'remarkup-assist-bar', ), - implode('', $buttons)); + $buttons); $monospaced_textareas = null; $monospaced_textareas_class = null; $user = $this->getUser(); if ($user) { $monospaced_textareas = $user ->loadPreferences() ->getPreference( PhabricatorUserPreferences::PREFERENCE_MONOSPACED_TEXTAREAS); if ($monospaced_textareas == 'enabled') { $monospaced_textareas_class = 'PhabricatorMonospaced'; } } $this->setCustomClass( 'remarkup-assist-textarea '.$monospaced_textareas_class); return javelin_render_tag( 'div', array( 'sigil' => 'remarkup-assist-control', ), $buttons. parent::renderInput()); } } diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 5aa00d9ddc..b5a6cf5792 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -1,138 +1,138 @@ href = $href; return $this; } public function setIcon($icon) { $this->icon = $icon; return $this; } public function setName($name) { $this->name = $name; return $this; } public function setDisabled($disabled) { $this->disabled = $disabled; return $this; } public function setWorkflow($workflow) { $this->workflow = $workflow; return $this; } public function setRenderAsForm($form) { $this->renderAsForm = $form; return $this; } public function render() { $icon = null; if ($this->icon) { $suffix = ''; if ($this->disabled) { $suffix = '-grey'; } require_celerity_resource('sprite-icon-css'); $icon = phutil_tag( 'span', array( 'class' => 'phabricator-action-view-icon sprite-icon '. 'action-'.$this->icon.$suffix, ), ''); } if ($this->href) { if ($this->renderAsForm) { if (!$this->user) { throw new Exception( 'Call setUser() when rendering an action as a form.'); } $item = javelin_render_tag( 'button', array( 'class' => 'phabricator-action-view-item', ), phutil_escape_html($this->name)); $item = phabricator_render_form( $this->user, array( 'action' => $this->href, 'method' => 'POST', 'sigil' => $this->workflow ? 'workflow' : null, ), $item); } else { $item = javelin_render_tag( 'a', array( 'href' => $this->href, 'class' => 'phabricator-action-view-item', 'sigil' => $this->workflow ? 'workflow' : null, ), phutil_escape_html($this->name)); } } else { $item = phutil_tag( 'span', array( 'class' => 'phabricator-action-view-item', ), $this->name); } $classes = array(); $classes[] = 'phabricator-action-view'; if ($this->disabled) { $classes[] = 'phabricator-action-view-disabled'; } - return phutil_render_tag( + return phutil_tag( 'li', array( 'class' => implode(' ', $classes), ), - $icon.$item); + array($icon, $item)); } public static function getAvailableIcons() { $root = dirname(phutil_get_library_root('phabricator')); $path = $root.'/resources/sprite/manifest/icon.json'; $data = Filesystem::readFile($path); $manifest = json_decode($data, true); $results = array(); $prefix = 'action-'; foreach ($manifest['sprites'] as $sprite) { $name = $sprite['name']; if (preg_match('/-(white|grey)$/', $name)) { continue; } if (!strncmp($name, $prefix, strlen($prefix))) { $results[] = substr($name, strlen($prefix)); } } return $results; } } diff --git a/src/view/layout/PhabricatorCrumbView.php b/src/view/layout/PhabricatorCrumbView.php index 2e422a1951..332d2f5465 100644 --- a/src/view/layout/PhabricatorCrumbView.php +++ b/src/view/layout/PhabricatorCrumbView.php @@ -1,93 +1,93 @@ rawName = $raw_name; return $this; } public function setName($name) { $this->name = $name; return $this; } public function getNameForRender() { return nonempty($this->rawName, phutil_escape_html($this->name)); } public function setHref($href) { $this->href = $href; return $this; } public function setIcon($icon) { $this->icon = $icon; return $this; } protected function canAppendChild() { return false; } public function setIsLastCrumb($is_last_crumb) { $this->isLastCrumb = $is_last_crumb; return $this; } public function render() { $classes = array( 'phabricator-crumb-view', ); $icon = null; if ($this->icon) { $classes[] = 'phabricator-crumb-has-icon'; $icon = phutil_tag( 'span', array( 'class' => 'phabricator-crumb-icon '. 'sprite-apps-large app-'.$this->icon.'-dark-large', ), ''); } $name = phutil_render_tag( 'span', array( 'class' => 'phabricator-crumb-name', ), $this->getNameForRender()); $divider = null; if (!$this->isLastCrumb) { $divider = phutil_tag( 'span', array( 'class' => 'sprite-menu phabricator-crumb-divider', ), ''); } - return phutil_render_tag( + return phutil_tag( $this->href ? 'a' : 'span', array( 'href' => $this->href, 'class' => implode(' ', $classes), ), - $icon.$name.$divider); + array($icon, $name, $divider)); } } diff --git a/src/view/layout/PhabricatorHeaderView.php b/src/view/layout/PhabricatorHeaderView.php index 8d2d7a911b..60a9e0655e 100644 --- a/src/view/layout/PhabricatorHeaderView.php +++ b/src/view/layout/PhabricatorHeaderView.php @@ -1,61 +1,64 @@ header = $header; return $this; } public function setObjectName($object_name) { $this->objectName = $object_name; return $this; } public function addTag(PhabricatorTagView $tag) { $this->tags[] = $tag; return $this; } public function render() { require_celerity_resource('phabricator-header-view-css'); - $header = phutil_escape_html($this->header); + $header = array($this->header); if ($this->objectName) { - $header = phutil_tag( - 'a', - array( - 'href' => '/'.$this->objectName, - ), - $this->objectName).' '.$header; + array_unshift( + $header, + phutil_tag( + 'a', + array( + 'href' => '/'.$this->objectName, + ), + $this->objectName), + ' '); } if ($this->tags) { - $header .= phutil_render_tag( + $header[] = phutil_render_tag( 'span', array( 'class' => 'phabricator-header-tags', ), self::renderSingleView($this->tags)); } return phutil_render_tag( 'div', array( 'class' => 'phabricator-header-shell', ), - phutil_render_tag( + phutil_tag( 'h1', array( 'class' => 'phabricator-header-view', ), $header)); } } diff --git a/src/view/layout/PhabricatorObjectItemView.php b/src/view/layout/PhabricatorObjectItemView.php index 018cfe6ac3..662e63a783 100644 --- a/src/view/layout/PhabricatorObjectItemView.php +++ b/src/view/layout/PhabricatorObjectItemView.php @@ -1,176 +1,176 @@ effect = $effect; return $this; } public function getEffect() { return $this->effect; } public function setObject($object) { $this->object = $object; return $this; } public function getObject() { return $this->object; } public function setHref($href) { $this->href = $href; return $this; } public function getHref() { return $this->href; } public function setHeader($header) { $this->header = $header; return $this; } public function getHeader() { return $this->header; } public function addIcon($icon, $label = null) { $this->icons[] = array( 'icon' => $icon, 'label' => $label, ); return $this; } public function setBarColor($bar_color) { $this->barColor = $bar_color; return $this; } public function getBarColor() { return $this->barColor; } public function addAttribute($attribute) { $this->attributes[] = $attribute; return $this; } public function render() { $header = phutil_tag( 'a', array( 'href' => $this->href, 'class' => 'phabricator-object-item-name', ), $this->header); $icons = null; if ($this->icons) { $icon_list = array(); foreach ($this->icons as $spec) { $icon = $spec['icon']; $icon = phutil_tag( 'span', array( 'class' => 'phabricator-object-item-icon-image '. 'sprite-icon action-'.$icon, ), ''); $label = phutil_tag( 'span', array( 'class' => 'phabricator-object-item-icon-label', ), $spec['label']); - $icon_list[] = phutil_render_tag( + $icon_list[] = phutil_tag( 'li', array( 'class' => 'phabricator-object-item-icon', ), - $label.$icon); + array($label, $icon)); } - $icons = phutil_render_tag( + $icons = phutil_tag( 'ul', array( 'class' => 'phabricator-object-item-icons', ), - implode('', $icon_list)); + $icon_list); } $attrs = null; if ($this->attributes) { $attrs = array(); $spacer = phutil_tag( 'span', array( 'class' => 'phabricator-object-item-attribute-spacer', ), "\xC2\xB7"); $first = true; foreach ($this->attributes as $attribute) { $attrs[] = phutil_render_tag( 'li', array( 'class' => 'phabricator-object-item-attribute', ), ($first ? null : $spacer).$attribute); $first = false; } - $attrs = phutil_render_tag( + $attrs = phutil_tag( 'ul', array( 'class' => 'phabricator-object-item-attributes', ), - implode('', $attrs)); + $attrs); } $classes = array(); $classes[] = 'phabricator-object-item'; if ($this->barColor) { $classes[] = 'phabricator-object-item-bar-color-'.$this->barColor; } switch ($this->effect) { case 'highlighted': $classes[] = 'phabricator-object-item-highlighted'; break; case null: break; default: throw new Exception("Invalid effect!"); } $content = phutil_render_tag( 'div', array( 'class' => 'phabricator-object-item-content', ), $header.$attrs.$this->renderChildren()); - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => implode(' ', $classes), ), - $icons.$content); + array($icons, $content)); } } diff --git a/src/view/layout/PhabricatorPinboardItemView.php b/src/view/layout/PhabricatorPinboardItemView.php index 7f97928c31..ed8b2e2998 100644 --- a/src/view/layout/PhabricatorPinboardItemView.php +++ b/src/view/layout/PhabricatorPinboardItemView.php @@ -1,79 +1,77 @@ header = $header; return $this; } public function setURI($uri) { $this->uri = $uri; return $this; } public function setImageURI($image_uri) { $this->imageURI = $image_uri; return $this; } public function setImageSize($x, $y) { $this->imageWidth = $x; $this->imageHeight = $y; return $this; } public function render() { $header = null; if ($this->header) { $header = hsprintf('
    %s', $this->uri, $this->header); - $header = phutil_render_tag( + $header = phutil_tag( 'div', array( 'class' => 'phabricator-pinboard-item-header', ), $header); } $image = phutil_render_tag( 'a', array( 'href' => $this->uri, 'class' => 'phabricator-pinboard-item-image-link', ), phutil_tag( 'img', array( 'src' => $this->imageURI, 'width' => $this->imageWidth, 'height' => $this->imageHeight, ))); $content = $this->renderChildren(); if ($content) { $content = phutil_render_tag( 'div', array( 'class' => 'phabricator-pinboard-item-content', ), $content); } - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'phabricator-pinboard-item-view', ), - $header. - $image. - $content); + array($header, $image, $content)); } } diff --git a/src/view/layout/PhabricatorPropertyListView.php b/src/view/layout/PhabricatorPropertyListView.php index 4fb140b04b..d89ee97fb4 100644 --- a/src/view/layout/PhabricatorPropertyListView.php +++ b/src/view/layout/PhabricatorPropertyListView.php @@ -1,157 +1,161 @@ hasKeyboardShortcuts = $has_keyboard_shortcuts; return $this; } public function addProperty($key, $value) { $current = array_pop($this->parts); if (!$current || $current['type'] != 'property') { if ($current) { $this->parts[] = $current; } $current = array( 'type' => 'property', 'list' => array(), ); } $current['list'][] = array( 'key' => $key, 'value' => $value, ); $this->parts[] = $current; return $this; } public function addSectionHeader($name) { $this->parts[] = array( 'type' => 'section', 'name' => $name, ); return $this; } public function addTextContent($content) { $this->parts[] = array( 'type' => 'text', 'content' => $content, ); return $this; } public function render() { require_celerity_resource('phabricator-property-list-view-css'); $items = array(); foreach ($this->parts as $part) { $type = $part['type']; switch ($type) { case 'property': $items[] = $this->renderPropertyPart($part); break; case 'section': $items[] = $this->renderSectionPart($part); break; case 'text': $items[] = $this->renderTextPart($part); break; default: throw new Exception("Unknown part type '{$type}'!"); } } return phutil_render_tag( 'div', array( 'class' => 'phabricator-property-list-view', ), $this->renderSingleView($items)); } private function renderPropertyPart(array $part) { $items = array(); foreach ($part['list'] as $spec) { $key = $spec['key']; $value = $spec['value']; $items[] = phutil_tag( 'dt', array( 'class' => 'phabricator-property-list-key', ), $key); $items[] = phutil_render_tag( 'dd', array( 'class' => 'phabricator-property-list-value', ), $this->renderSingleView($value)); } $list = phutil_render_tag( 'dl', array( 'class' => 'phabricator-property-list-properties', ), $this->renderSingleView($items)); $content = $this->renderChildren(); if (strlen($content)) { $content = phutil_render_tag( 'div', array( 'class' => 'phabricator-property-list-content', ), $content); } $shortcuts = null; if ($this->hasKeyboardShortcuts) { $shortcuts = id(new AphrontKeyboardShortcutsAvailableView())->render(); } return $shortcuts. phutil_render_tag( 'div', array( 'class' => 'phabricator-property-list-container', ), - $list. - '
    ' - ); + array( + $list, + phutil_tag( + 'div', + array('class' => 'phabriator-property-list-view-end'), + ''), + )); } private function renderSectionPart(array $part) { return phutil_tag( 'div', array( 'class' => 'phabricator-property-list-section-header', ), $part['name']); } private function renderTextPart(array $part) { return phutil_render_tag( 'div', array( 'class' => 'phabricator-property-list-text-content', ), $part['content']); } } diff --git a/src/view/layout/PhabricatorTagView.php b/src/view/layout/PhabricatorTagView.php index 8755c048ef..c28ba9cecd 100644 --- a/src/view/layout/PhabricatorTagView.php +++ b/src/view/layout/PhabricatorTagView.php @@ -1,167 +1,167 @@ type = $type; switch ($type) { case self::TYPE_OBJECT: $this->setBackgroundColor(self::COLOR_OBJECT); break; case self::TYPE_PERSON: $this->setBackgroundColor(self::COLOR_PERSON); break; } return $this; } public function setBarColor($bar_color) { $this->barColor = $bar_color; return $this; } public function setDotColor($dot_color) { $this->dotColor = $dot_color; return $this; } public function setBackgroundColor($background_color) { $this->backgroundColor = $background_color; return $this; } public function setPHID($phid) { $this->phid = $phid; return $this; } public function setName($name) { $this->name = $name; return $this; } public function setHref($href) { $this->href = $href; return $this; } public function setClosed($closed) { $this->closed = $closed; return $this; } public function render() { if (!$this->type) { throw new Exception("You must call setType() before render()!"); } require_celerity_resource('phabricator-tag-view-css'); $classes = array( 'phabricator-tag-view', 'phabricator-tag-type-'.$this->type, ); if ($this->closed) { $classes[] = 'phabricator-tag-state-closed'; } $color = null; if ($this->backgroundColor) { $color = 'phabricator-tag-color-'.$this->backgroundColor; } if ($this->dotColor) { $dotcolor = 'phabricator-tag-color-'.$this->dotColor; $dot = phutil_tag( 'span', array( 'class' => 'phabricator-tag-dot '.$dotcolor, ), ''); } else { $dot = null; } - $content = phutil_render_tag( + $content = phutil_tag( 'span', array( 'class' => 'phabricator-tag-core '.$color, ), - $dot.phutil_escape_html($this->name)); + array($dot, $this->name)); if ($this->barColor) { $barcolor = 'phabricator-tag-color-'.$this->barColor; $bar = phutil_tag( 'span', array( 'class' => 'phabricator-tag-bar '.$barcolor, ), ''); $classes[] = 'phabricator-tag-view-has-bar'; } else { $bar = null; } - return phutil_render_tag( + return phutil_tag( $this->href ? 'a' : 'span', array( 'href' => $this->href, 'class' => implode(' ', $classes), ), - $bar.$content); + array($bar, $content)); } public static function getTagTypes() { return array( self::TYPE_PERSON, self::TYPE_OBJECT, self::TYPE_STATE, ); } public static function getColors() { return array( self::COLOR_RED, self::COLOR_REDORANGE, self::COLOR_ORANGE, self::COLOR_YELLOW, self::COLOR_BLUE, self::COLOR_MAGENTA, self::COLOR_GREEN, self::COLOR_BLACK, self::COLOR_GREY, self::COLOR_WHITE, self::COLOR_OBJECT, self::COLOR_PERSON, ); } } diff --git a/src/view/layout/PhabricatorTimelineEventView.php b/src/view/layout/PhabricatorTimelineEventView.php index 7b0eb5546b..4359a5c9c0 100644 --- a/src/view/layout/PhabricatorTimelineEventView.php +++ b/src/view/layout/PhabricatorTimelineEventView.php @@ -1,300 +1,300 @@ transactionPHID = $transaction_phid; return $this; } public function getTransactionPHID() { return $this->transactionPHID; } public function setIsEdited($is_edited) { $this->isEdited = $is_edited; return $this; } public function getIsEdited() { return $this->isEdited; } public function setIsPreview($is_preview) { $this->isPreview = $is_preview; return $this; } public function getIsPreview() { return $this->isPreview; } public function setIsEditable($is_editable) { $this->isEditable = $is_editable; return $this; } public function getIsEditable() { return $this->isEditable; } public function setDateCreated($date_created) { $this->dateCreated = $date_created; return $this; } public function getDateCreated() { return $this->dateCreated; } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source; return $this; } public function getContentSource() { return $this->contentSource; } public function setUserHandle(PhabricatorObjectHandle $handle) { $this->userHandle = $handle; return $this; } public function setAnchor($anchor) { $this->anchor = $anchor; return $this; } public function setTitle($title) { $this->title = $title; return $this; } public function addClass($class) { $this->classes[] = $class; return $this; } public function setIcon($icon) { $this->icon = $icon; return $this; } public function setColor($color) { $this->color = $color; return $this; } public function render() { $content = $this->renderChildren(); $title = $this->title; if (($title === null) && !strlen($content)) { $title = ''; } $extra = $this->renderExtra(); if ($title !== null || $extra !== null) { $title_classes = array(); $title_classes[] = 'phabricator-timeline-title'; $icon = null; if ($this->icon) { $title_classes[] = 'phabricator-timeline-title-with-icon'; $icon = phutil_render_tag( 'span', array( 'class' => 'phabricator-timeline-icon-fill', ), phutil_tag( 'span', array( 'class' => 'phabricator-timeline-icon sprite-icon '. 'action-'.$this->icon.'-white', ), '')); } $title = phutil_render_tag( 'div', array( 'class' => implode(' ', $title_classes), ), $title.$extra); $title = $icon.$title; } $wedge = phutil_tag( 'div', array( 'class' => 'phabricator-timeline-wedge phabricator-timeline-border', ), ''); $image_uri = $this->userHandle->getImageURI(); $image = phutil_tag( 'div', array( 'style' => 'background-image: url('.$image_uri.')', 'class' => 'phabricator-timeline-image', ), ''); $content_classes = array(); $content_classes[] = 'phabricator-timeline-content'; $classes = array(); $classes[] = 'phabricator-timeline-event-view'; $classes[] = 'phabricator-timeline-border'; if ($content) { $classes[] = 'phabricator-timeline-major-event'; $content = phutil_render_tag( 'div', array( 'class' => implode(' ', $content_classes), ), phutil_render_tag( 'div', array( 'class' => 'phabricator-timeline-inner-content', ), $title. phutil_render_tag( 'div', array( 'class' => 'phabricator-timeline-core-content', ), $content))); $content = $image.$wedge.$content; } else { $classes[] = 'phabricator-timeline-minor-event'; $content = phutil_render_tag( 'div', array( 'class' => implode(' ', $content_classes), ), $image.$wedge.$title); } $outer_classes = $this->classes; $outer_classes[] = 'phabricator-timeline-shell'; if ($this->color) { $outer_classes[] = 'phabricator-timeline-'.$this->color; } $sigil = null; $meta = null; if ($this->getTransactionPHID()) { $sigil = 'transaction'; $meta = array( 'phid' => $this->getTransactionPHID(), 'anchor' => $this->anchor, ); } return javelin_render_tag( 'div', array( 'class' => implode(' ', $outer_classes), 'id' => $this->anchor ? 'anchor-'.$this->anchor : null, 'sigil' => $sigil, 'meta' => $meta, ), phutil_render_tag( 'div', array( 'class' => implode(' ', $classes), ), $content)); } private function renderExtra() { $extra = array(); if ($this->getIsPreview()) { $extra[] = pht('PREVIEW'); } else { $xaction_phid = $this->getTransactionPHID(); if ($this->getIsEdited()) { $extra[] = javelin_render_tag( 'a', array( 'href' => '/transactions/history/'.$xaction_phid.'/', 'sigil' => 'workflow', ), pht('Edited')); } if ($this->getIsEditable()) { $extra[] = javelin_render_tag( 'a', array( 'href' => '/transactions/edit/'.$xaction_phid.'/', 'sigil' => 'workflow transaction-edit', ), pht('Edit')); } $source = $this->getContentSource(); if ($source) { $extra[] = id(new PhabricatorContentSourceView()) ->setContentSource($source) ->setUser($this->getUser()) ->render(); } if ($this->getDateCreated()) { $date = phabricator_datetime( $this->getDateCreated(), $this->getUser()); if ($this->anchor) { Javelin::initBehavior('phabricator-watch-anchor'); $anchor = id(new PhabricatorAnchorView()) ->setAnchorName($this->anchor) ->render(); - $date = $anchor.phutil_render_tag( + $date = $anchor.phutil_tag( 'a', array( 'href' => '#'.$this->anchor, ), $date); } $extra[] = $date; } } $extra = implode(' · ', $extra); if ($extra) { $extra = phutil_render_tag( 'span', array( 'class' => 'phabricator-timeline-extra', ), $extra); } return $extra; } } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 65bd6cddc0..a8ada196e7 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -1,370 +1,370 @@ applicationMenu = $application_menu; return $this; } public function getApplicationMenu() { return $this->applicationMenu; } public function setController(PhabricatorController $controller) { $this->controller = $controller; return $this; } public function getController() { return $this->controller; } public function setDefaultSearchScope($default_search_scope) { $this->defaultSearchScope = $default_search_scope; return $this; } public function getDefaultSearchScope() { return $this->defaultSearchScope; } public function render() { $user = $this->user; require_celerity_resource('phabricator-main-menu-view'); $header_id = celerity_generate_unique_node_id(); $menus = array(); $alerts = array(); if ($user->isLoggedIn()) { list($menu, $dropdown) = $this->renderNotificationMenu(); $alerts[] = $menu; $menus[] = $dropdown; } $phabricator_menu = $this->renderPhabricatorMenu(); if ($alerts) { $alerts = phutil_render_tag( 'div', array( 'class' => 'phabricator-main-menu-alerts', ), self::renderSingleView($alerts)); } $application_menu = $this->getApplicationMenu(); if ($application_menu) { $application_menu->addClass('phabricator-dark-menu'); $application_menu->addClass('phabricator-application-menu'); } return phutil_render_tag( 'div', array( 'class' => 'phabricator-main-menu', 'id' => $header_id, ), self::renderSingleView( array( $this->renderPhabricatorMenuButton($header_id), $application_menu ? $this->renderApplicationMenuButton($header_id) : null, $this->renderPhabricatorLogo(), $alerts, $phabricator_menu, $application_menu, ))). self::renderSingleView($menus); } private function renderSearch() { $user = $this->user; $result = null; $keyboard_config = array( 'helpURI' => '/help/keyboardshortcut/', ); if ($user->isLoggedIn()) { $search = new PhabricatorMainMenuSearchView(); $search->setUser($user); $search->setScope($this->getDefaultSearchScope()); $result = $search; $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; if ($user->loadPreferences()->getPreference($pref_shortcut, true)) { $keyboard_config['searchID'] = $search->getID(); } } Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config); if ($result) { $result = id(new PhabricatorMenuItemView()) ->addClass('phabricator-main-menu-search') ->appendChild($result); } return $result; } private function renderPhabricatorMenuButton($header_id) { return javelin_render_tag( 'a', array( 'class' => 'phabricator-main-menu-expand-button '. 'phabricator-expand-core-menu', 'sigil' => 'jx-toggle-class', 'meta' => array( 'map' => array( $header_id => 'phabricator-core-menu-expanded', ), ), ), phutil_tag( 'span', array( 'class' => 'phabricator-menu-button-icon sprite-menu menu-icon-eye', ), '')); } public function renderApplicationMenuButton($header_id) { return javelin_render_tag( 'a', array( 'class' => 'phabricator-main-menu-expand-button '. 'phabricator-expand-application-menu', 'sigil' => 'jx-toggle-class', 'meta' => array( 'map' => array( $header_id => 'phabricator-application-menu-expanded', ), ), ), phutil_tag( 'span', array( 'class' => 'phabricator-menu-button-icon sprite-menu menu-icon-app', ), '')); } private function renderPhabricatorMenu() { $user = $this->getUser(); $controller = $this->getController(); $applications = PhabricatorApplication::getAllInstalledApplications(); $applications = msort($applications, 'getName'); $core = array(); $more = array(); $actions = array(); require_celerity_resource('sprite-apps-large-css'); $group_core = PhabricatorApplication::GROUP_CORE; foreach ($applications as $application) { if ($application->shouldAppearInLaunchView()) { $icon = $application->getIconName().'-light-large'; $item = id(new PhabricatorMenuItemView()) ->setName($application->getName()) ->setHref($application->getBaseURI()) ->appendChild($this->renderMenuIcon($icon)); if ($application->getApplicationGroup() == $group_core) { $core[] = $item; } else { $more[] = $item; } } $app_actions = $application->buildMainMenuItems($user, $controller); foreach ($app_actions as $action) { $actions[] = $action; } } $view = new PhabricatorMenuView(); $view->addClass('phabricator-dark-menu'); $view->addClass('phabricator-core-menu'); $search = $this->renderSearch(); $view->appendChild($search); $view ->newLabel(pht('Home')) ->addClass('phabricator-core-item-device'); $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setName(pht('Phabricator Home')) ->setHref('/') ->appendChild($this->renderMenuIcon('logo-light-large'))); if ($controller && $controller->getCurrentApplication()) { $application = $controller->getCurrentApplication(); $icon = $application->getIconName().'-light-large'; $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setName(pht('%s Home', $application->getName())) ->appendChild($this->renderMenuIcon($icon)) ->setHref($controller->getApplicationURI())); } if ($core) { $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setType(PhabricatorMenuItemView::TYPE_LABEL) ->setName(pht('Core Applications'))); foreach ($core as $item) { $item->addClass('phabricator-core-item-device'); $view->addMenuItem($item); } } if ($actions) { $actions = msort($actions, 'getSortOrder'); $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setType(PhabricatorMenuItemView::TYPE_LABEL) ->setName(pht('Actions'))); foreach ($actions as $action) { $icon = $action->getIcon(); if ($icon) { if ($action->getSelected()) { $action->appendChild($this->renderMenuIcon($icon.'-blue-large')); } else { $action->appendChild($this->renderMenuIcon($icon.'-light-large')); } } $view->addMenuItem($action); } } if ($more) { $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setType(PhabricatorMenuItemView::TYPE_LABEL) ->setName(pht('More Applications'))); foreach ($more as $item) { $item->addClass('phabricator-core-item-device'); $view->addMenuItem($item); } } return $view; } private function renderPhabricatorLogo() { return phutil_render_tag( 'a', array( 'class' => 'phabricator-main-menu-logo', 'href' => '/', ), phutil_tag( 'span', array( 'class' => 'sprite-menu phabricator-main-menu-logo-image', ), '')); } private function renderNotificationMenu() { $user = $this->user; require_celerity_resource('phabricator-notification-css'); require_celerity_resource('phabricator-notification-menu-css'); require_celerity_resource('sprite-menu-css'); $count_id = celerity_generate_unique_node_id(); $dropdown_id = celerity_generate_unique_node_id(); $bubble_id = celerity_generate_unique_node_id(); $count_number = id(new PhabricatorFeedStoryNotification()) ->countUnread($user); if ($count_number > 999) { $count_number = "\xE2\x88\x9E"; } $count_tag = phutil_tag( 'span', array( 'id' => $count_id, 'class' => 'phabricator-main-menu-alert-count' ), $count_number); $icon_tag = phutil_tag( 'span', array( 'class' => 'sprite-menu phabricator-main-menu-alert-icon', ), ''); $container_classes = array( 'phabricator-main-menu-alert-bubble', 'sprite-menu', 'alert-notifications', ); if ($count_number) { $container_classes[] = 'alert-unread'; } - $bubble_tag = phutil_render_tag( + $bubble_tag = phutil_tag( 'a', array( 'href' => '/notification/', 'class' => implode(' ', $container_classes), 'id' => $bubble_id, ), - $icon_tag.$count_tag); + array($icon_tag, $count_tag)); Javelin::initBehavior( 'aphlict-dropdown', array( 'bubbleID' => $bubble_id, 'countID' => $count_id, 'dropdownID' => $dropdown_id, )); $notification_dropdown = javelin_render_tag( 'div', array( 'id' => $dropdown_id, 'class' => 'phabricator-notification-menu', 'sigil' => 'phabricator-notification-menu', 'style' => 'display: none;', ), ''); return array($bubble_tag, $notification_dropdown); } private function renderMenuIcon($name) { return phutil_tag( 'span', array( 'class' => 'phabricator-core-menu-icon '. 'sprite-apps-large app-'.$name, ), ''); } }