1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Added initial class for displaying invisible chars

Summary:
Fixes T11586. First pass at a class for displaying invisible characters. Still need to:
- Write a couple unit tests
- Add some styling to the .invisible-special spans
- Actually start using the class when displaying form errors to users

Currently this makes the string `"\nab\x00c\x01d\te\nf"` look like:

{F1812711}

Test Plan:
Unit tests all pass and run in <1ms:

{F1812998}

Reviewers: epriestley, #blessed_reviewers, chad

Reviewed By: epriestley, #blessed_reviewers

Subscribers: Korvin, epriestley, yelirekim

Maniphest Tasks: T11586

Differential Revision: https://secure.phabricator.com/D16541
This commit is contained in:
Josh Cox 2016-09-12 08:53:54 -04:00
parent ff64c4e02b
commit 2588b4fac0
7 changed files with 173 additions and 3 deletions

View file

@ -146,6 +146,7 @@ return array(
'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
'rsrc/css/phui/phui-info-view.css' => '28efab79', 'rsrc/css/phui/phui-info-view.css' => '28efab79',
'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0',
'rsrc/css/phui/phui-list.css' => '9da2aa00', 'rsrc/css/phui/phui-list.css' => '9da2aa00',
'rsrc/css/phui/phui-object-box.css' => '6b487c57', 'rsrc/css/phui/phui-object-box.css' => '6b487c57',
'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0', 'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0',
@ -922,6 +923,7 @@ return array(
'phui-info-panel-css' => '27ea50a1', 'phui-info-panel-css' => '27ea50a1',
'phui-info-view-css' => '28efab79', 'phui-info-view-css' => '28efab79',
'phui-inline-comment-view-css' => '5953c28e', 'phui-inline-comment-view-css' => '5953c28e',
'phui-invisible-character-view-css' => '6993d9f0',
'phui-list-view-css' => '9da2aa00', 'phui-list-view-css' => '9da2aa00',
'phui-object-box-css' => '6b487c57', 'phui-object-box-css' => '6b487c57',
'phui-object-item-list-view-css' => '87278fa0', 'phui-object-item-list-view-css' => '87278fa0',

View file

@ -1652,6 +1652,8 @@ phutil_register_library_map(array(
'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php', 'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php',
'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php', 'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php',
'PHUIInfoView' => 'view/form/PHUIInfoView.php', 'PHUIInfoView' => 'view/form/PHUIInfoView.php',
'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php',
'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php',
'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php', 'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php',
'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListItemView' => 'view/phui/PHUIListItemView.php',
'PHUIListView' => 'view/phui/PHUIListView.php', 'PHUIListView' => 'view/phui/PHUIListView.php',
@ -6320,6 +6322,8 @@ phutil_register_library_map(array(
'PHUIInfoPanelExample' => 'PhabricatorUIExample', 'PHUIInfoPanelExample' => 'PhabricatorUIExample',
'PHUIInfoPanelView' => 'AphrontView', 'PHUIInfoPanelView' => 'AphrontView',
'PHUIInfoView' => 'AphrontView', 'PHUIInfoView' => 'AphrontView',
'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase',
'PHUIInvisibleCharacterView' => 'AphrontView',
'PHUIListExample' => 'PhabricatorUIExample', 'PHUIListExample' => 'PhabricatorUIExample',
'PHUIListItemView' => 'AphrontTagView', 'PHUIListItemView' => 'AphrontTagView',
'PHUIListView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView',

View file

@ -89,13 +89,14 @@ final class PhabricatorAuthRegisterController
// user expectation and it's not clear the cases it enables are valuable. // user expectation and it's not clear the cases it enables are valuable.
// See discussion in T3472. // See discussion in T3472.
if (!PhabricatorUserEmail::isAllowedAddress($default_email)) { if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
$debug_email = new PHUIInvisibleCharacterView($default_email);
return $this->renderError( return $this->renderError(
array( array(
pht( pht(
'The account you are attempting to register with has an invalid '. 'The account you are attempting to register with has an invalid '.
'email address (%s). This Phabricator install only allows '. 'email address (%s). This Phabricator install only allows '.
'registration with specific email addresses:', 'registration with specific email addresses:',
$default_email), $debug_email),
phutil_tag('br'), phutil_tag('br'),
phutil_tag('br'), phutil_tag('br'),
PhabricatorUserEmail::describeAllowedAddresses(), PhabricatorUserEmail::describeAllowedAddresses(),

View file

@ -123,18 +123,23 @@ final class PhabricatorPhurlURLEditor
foreach ($xactions as $xaction) { foreach ($xactions as $xaction) {
if ($xaction->getOldValue() != $xaction->getNewValue()) { if ($xaction->getOldValue() != $xaction->getNewValue()) {
$new_alias = $xaction->getNewValue(); $new_alias = $xaction->getNewValue();
$debug_alias = new PHUIInvisibleCharacterView($new_alias);
if (!preg_match('/[a-zA-Z]/', $new_alias)) { if (!preg_match('/[a-zA-Z]/', $new_alias)) {
$errors[] = new PhabricatorApplicationTransactionValidationError( $errors[] = new PhabricatorApplicationTransactionValidationError(
$type, $type,
pht('Invalid Alias'), pht('Invalid Alias'),
pht('The alias must contain at least one letter.'), pht('The alias you provided (%s) must contain at least one '.
'letter.',
$debug_alias),
$xaction); $xaction);
} }
if (preg_match('/[^a-z0-9]/i', $new_alias)) { if (preg_match('/[^a-z0-9]/i', $new_alias)) {
$errors[] = new PhabricatorApplicationTransactionValidationError( $errors[] = new PhabricatorApplicationTransactionValidationError(
$type, $type,
pht('Invalid Alias'), pht('Invalid Alias'),
pht('The alias may only contain letters and numbers.'), pht('The alias you provided (%s) may only contain letters and '.
'numbers.',
$debug_alias),
$xaction); $xaction);
} }
} }

View file

@ -0,0 +1,94 @@
<?php
/**
* API for replacing whitespace characters and some control characters with
* their printable representations. This is useful for debugging and
* displaying more helpful error messages to users.
*
*/
final class PHUIInvisibleCharacterView extends AphrontView {
private $inputText;
private $plainText = false;
// This is a list of the common invisible characters that are
// actually typeable. Other invisible characters will simply
// be displayed as their hex representations.
private static $invisibleChars = array(
"\x00" => 'NULL',
"\t" => 'TAB',
"\n" => 'NEWLINE',
"\x20" => 'SPACE',
);
public function __construct($input_text) {
$this->inputText = $input_text;
}
public function setPlainText($plain_text) {
$this->plainText = $plain_text;
return $this;
}
public function getStringParts() {
$input_text = $this->inputText;
$text_array = phutil_utf8v($input_text);
for ($ii = 0; $ii < count($text_array); $ii++) {
$char = $text_array[$ii];
$char_hex = bin2hex($char);
if (array_key_exists($char, self::$invisibleChars)) {
$text_array[$ii] = array(
'special' => true,
'value' => '<'.self::$invisibleChars[$char].'>',
);
} else if (ord($char) < 32) {
$text_array[$ii] = array(
'special' => true,
'value' => '<0x'.$char_hex.'>',
);
} else {
$text_array[$ii] = array(
'special' => false,
'value' => $char,
);
}
}
return $text_array;
}
private function renderHtmlArray() {
$html_array = array();
$parts = $this->getStringParts();
foreach ($parts as $part) {
if ($part['special']) {
$html_array[] = phutil_tag(
'span',
array('class' => 'invisible-special'),
$part['value']);
} else {
$html_array[] = $part['value'];
}
}
return $html_array;
}
private function renderPlainText() {
$parts = $this->getStringParts();
$res = '';
foreach ($parts as $part) {
$res .= $part['value'];
}
return $res;
}
public function render() {
require_celerity_resource('phui-invisible-character-view-css');
if ($this->plainText) {
return $this->renderPlainText();
} else {
return $this->renderHtmlArray();
}
}
}

View file

@ -0,0 +1,52 @@
<?php
final class PHUIInvisibleCharacterTestCase extends PhabricatorTestCase {
public function testEmptyString() {
$view = new PHUIInvisibleCharacterView('');
$res = $view->render();
$this->assertEqual($res, array());
}
public function testEmptyPlainText() {
$view = (new PHUIInvisibleCharacterView(''))
->setPlainText(true);
$res = $view->render();
$this->assertEqual($res, '');
}
public function testWithNamedChars() {
$test_input = "\x00\n\t ";
$view = (new PHUIInvisibleCharacterView($test_input))
->setPlainText(true);
$res = $view->render();
$this->assertEqual($res, '<NULL><NEWLINE><TAB><SPACE>');
}
public function testWithHexChars() {
$test_input = "abc\x01";
$view = (new PHUIInvisibleCharacterView($test_input))
->setPlainText(true);
$res = $view->render();
$this->assertEqual($res, 'abc<0x01>');
}
public function testWithNamedAsHex() {
$test_input = "\x00\x0a\x09\x20";
$view = (new PHUIInvisibleCharacterView($test_input))
->setPlainText(true);
$res = $view->render();
$this->assertEqual($res, '<NULL><NEWLINE><TAB><SPACE>');
}
public function testHtmlDecoration() {
$test_input = "a\x00\n\t ";
$view = new PHUIInvisibleCharacterView($test_input);
$res = $view->render();
$this->assertFalse($res[0] instanceof PhutilSafeHTML);
$this->assertTrue($res[1] instanceof PhutilSafeHTML);
$this->assertTrue($res[2] instanceof PhutilSafeHTML);
$this->assertTrue($res[3] instanceof PhutilSafeHTML);
$this->assertTrue($res[4] instanceof PhutilSafeHTML);
}
}

View file

@ -0,0 +1,12 @@
/**
* @provides phui-invisible-character-view-css
*/
.invisible-special {
font-family: monospace;
color: #000;
background: rgba({$alphablue},0.1);
padding: 1px 4px;
border-radius: 3px;
white-space: pre-wrap;
}