1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-16 16:58:38 +01:00

Promote 2023.32 to Stable

This commit is contained in:
Aviv Eyal 2023-08-25 04:52:45 -07:00
commit 11424ffbe6
113 changed files with 1044 additions and 192 deletions

View file

@ -9,7 +9,7 @@ return array(
'names' => array(
'conpherence.pkg.css' => '76ed87e3',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '0cb47e9d',
'core.pkg.css' => '1a5169fe',
'core.pkg.js' => '2eeda9e0',
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => '525f9a1d',
@ -100,7 +100,7 @@ return array(
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
'rsrc/css/application/policy/policy-transaction-detail.css' => 'c02b8384',
'rsrc/css/application/policy/policy.css' => 'ceb56a08',
'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a',
'rsrc/css/application/ponder/ponder-view.css' => 'b04bbaff',
'rsrc/css/application/project/project-card-view.css' => 'c1200da7',
'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9',
'rsrc/css/application/project/project-view.css' => '2f7caa20',
@ -134,7 +134,7 @@ return array(
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => '9275ff55',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '9b03a61f',
'rsrc/css/phui/phui-action-list.css' => '1b0085b2',
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
'rsrc/css/phui/phui-badge.css' => '666e25ad',
@ -154,25 +154,25 @@ return array(
'rsrc/css/phui/phui-document.css' => '52b748a5',
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
'rsrc/css/phui/phui-form-view.css' => '7536aef9',
'rsrc/css/phui/phui-form-view.css' => '57edecb7',
'rsrc/css/phui/phui-form.css' => 'd1adb52c',
'rsrc/css/phui/phui-formation-view.css' => 'd2dec8ed',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '36c86a58',
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
'rsrc/css/phui/phui-hovercard.css' => '39fd2e14',
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
'rsrc/css/phui/phui-icon.css' => '084ac612',
'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2',
'rsrc/css/phui/phui-info-view.css' => 'a10a909b',
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '0c04affd',
'rsrc/css/phui/phui-list.css' => 'ccf73664',
'rsrc/css/phui/phui-object-box.css' => 'fdffed5c',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
'rsrc/css/phui/phui-policy-section-view.css' => '139fdc64',
'rsrc/css/phui/phui-property-list-view.css' => '5adf7078',
'rsrc/css/phui/phui-property-list-view.css' => '118db252',
'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
'rsrc/css/phui/phui-spacing.css' => 'b05cadc3',
@ -184,7 +184,7 @@ return array(
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
'rsrc/css/phui/workboards/phui-workcard.css' => '62056e3b',
'rsrc/css/phui/workboards/phui-workpanel.css' => 'bc06f022',
'rsrc/css/sprite-login.css' => '35d1510c',
'rsrc/css/sprite-login.css' => '07052ee0',
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
'rsrc/externals/d3/d3.min.js' => '9d068042',
@ -342,8 +342,8 @@ return array(
'rsrc/image/phrequent_active.png' => 'de66dc50',
'rsrc/image/phrequent_inactive.png' => '79c61baf',
'rsrc/image/resize.png' => '9cc83373',
'rsrc/image/sprite-login-X2.png' => '269800ec',
'rsrc/image/sprite-login.png' => 'a843f146',
'rsrc/image/sprite-login-X2.png' => '02896524',
'rsrc/image/sprite-login.png' => 'e0508107',
'rsrc/image/sprite-tokens-X2.png' => '21621dd9',
'rsrc/image/sprite-tokens.png' => 'bede2580',
'rsrc/image/texture/card-gradient.png' => 'e6892cb4',
@ -387,7 +387,7 @@ return array(
'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b',
'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd',
'rsrc/js/application/differential/behavior-populate.js' => 'b86ef6c2',
'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '94243d89',
'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '6c798a10',
'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f',
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
@ -707,7 +707,7 @@ return array(
'javelin-chart-function-label' => '81de1dab',
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
'javelin-diffusion-locate-file-source' => '6c798a10',
'javelin-dom' => 'e4c7622a',
'javelin-dynval' => '202a2e85',
'javelin-event' => 'c03f2fb4',
@ -849,33 +849,33 @@ return array(
'phui-font-icon-base-css' => '303c9b87',
'phui-fontkit-css' => '1ec937e5',
'phui-form-css' => 'd1adb52c',
'phui-form-view-css' => '7536aef9',
'phui-form-view-css' => '57edecb7',
'phui-formation-view-css' => 'd2dec8ed',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
'phui-hovercard' => '6199f752',
'phui-hovercard-list' => 'de4b4919',
'phui-hovercard-view-css' => '6ca90fa0',
'phui-hovercard-view-css' => '39fd2e14',
'phui-icon-set-selector-css' => '7aa5f3ec',
'phui-icon-view-css' => '4cbc684a',
'phui-icon-view-css' => '084ac612',
'phui-image-mask-css' => '62c7f4d2',
'phui-info-view-css' => 'a10a909b',
'phui-inline-comment-view-css' => 'a864426f',
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '0c04affd',
'phui-list-view-css' => 'ccf73664',
'phui-object-box-css' => 'fdffed5c',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
'phui-oi-flush-ui-css' => '490e2e2e',
'phui-oi-list-view-css' => '9275ff55',
'phui-oi-simple-ui-css' => '6a30fa46',
'phui-oi-simple-ui-css' => '9b03a61f',
'phui-pager-css' => 'd022c7ad',
'phui-pinboard-view-css' => '1f08f5d8',
'phui-policy-section-view-css' => '139fdc64',
'phui-property-list-view-css' => '5adf7078',
'phui-property-list-view-css' => '118db252',
'phui-remarkup-preview-css' => '91767007',
'phui-segment-bar-view-css' => '5166b370',
'phui-spacing-css' => 'b05cadc3',
@ -901,12 +901,12 @@ return array(
'policy-css' => 'ceb56a08',
'policy-edit-css' => '8794e2ed',
'policy-transaction-detail-css' => 'c02b8384',
'ponder-view-css' => '05a09d0a',
'ponder-view-css' => 'b04bbaff',
'project-card-view-css' => 'c1200da7',
'project-triggers-css' => 'cd9c8bb9',
'project-view-css' => '2f7caa20',
'setup-issue-css' => '5eed85b2',
'sprite-login-css' => '35d1510c',
'sprite-login-css' => '07052ee0',
'sprite-tokens-css' => 'f1896dc5',
'syntax-default-css' => '055fc231',
'syntax-highlighting-css' => '548567f6',
@ -1538,9 +1538,6 @@ return array(
'6a18c42e' => array(
'javelin-install',
),
'6a30fa46' => array(
'phui-oi-list-view-css',
),
'6a85bc5a' => array(
'javelin-behavior',
'javelin-dom',
@ -1555,6 +1552,12 @@ return array(
'phabricator-textareautils',
'phabricator-remarkup-metadata',
),
'6c798a10' => array(
'javelin-install',
'javelin-dom',
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'6cfa0008' => array(
'javelin-dom',
'javelin-dynval',
@ -1762,12 +1765,6 @@ return array(
'phabricator-prefab',
'javelin-json',
),
'94243d89' => array(
'javelin-install',
'javelin-dom',
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'9623adc1' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1791,6 +1788,9 @@ return array(
'javelin-install',
'javelin-util',
),
'9b03a61f' => array(
'phui-oi-list-view-css',
),
'9c01e364' => array(
'javelin-behavior',
'javelin-dom',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 773 B

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 B

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

View file

@ -24,7 +24,7 @@
"login-Facebook": {
"name": "login-Facebook",
"rule": ".login-Facebook",
"hash": "1b12a5a5cfe103d4d96213cf7d1ce18a"
"hash": "ad9b8fcc4c338bfe4196a7f636c7d4c1"
},
"login-Generic": {
"name": "login-Generic",
@ -39,7 +39,7 @@
"login-Google": {
"name": "login-Google",
"rule": ".login-Google",
"hash": "72e7b0e1005c92f059f4d5881592ee72"
"hash": "9a25ee35dfde4135db2e321e01b82c61"
},
"login-HTTP": {
"name": "login-HTTP",
@ -71,6 +71,11 @@
"rule": ".login-Phabricator",
"hash": "54f5ddae4b9d138c438ec00ed42544d2"
},
"login-Phorge": {
"name": "login-Phorge",
"rule": ".login-Phorge",
"hash": "0cc36ea8b7b98c57c23e547400b955f1"
},
"login-Slack": {
"name": "login-Slack",
"rule": ".login-Slack",

View file

@ -466,6 +466,7 @@ phutil_register_library_map(array(
'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php',
'DifferentialBlockingReviewerDatasource' => 'applications/differential/typeahead/DifferentialBlockingReviewerDatasource.php',
'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php',
'DifferentialBranchFieldTestCase' => 'applications/differential/customfield/__tests__/DifferentialBranchFieldTestCase.php',
'DifferentialBuildableEngine' => 'applications/differential/harbormaster/DifferentialBuildableEngine.php',
'DifferentialChangeDetailMailView' => 'applications/differential/mail/DifferentialChangeDetailMailView.php',
'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php',
@ -1052,6 +1053,7 @@ phutil_register_library_map(array(
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
'DiffusionSearchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php',
'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
'DiffusionServeControllerTestCase' => 'applications/diffusion/controller/__tests__/DiffusionServeControllerTestCase.php',
'DiffusionServiceRef' => 'applications/diffusion/ref/DiffusionServiceRef.php',
'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
@ -3081,6 +3083,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardColumn' => 'applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php',
'PhabricatorDashboardConsoleController' => 'applications/dashboard/controller/PhabricatorDashboardConsoleController.php',
'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php',
'PhabricatorDashboardCreateCapability' => 'applications/countdown/capability/PhabricatorDashboardCreateCapability.php',
'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php',
'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php',
'PhabricatorDashboardDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardDatasource.php',
@ -5641,6 +5644,7 @@ phutil_register_library_map(array(
'PhrictionEditEngineController' => 'applications/phriction/controller/PhrictionEditEngineController.php',
'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php',
'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php',
'PhrictionHovercardEngineExtension' => 'applications/phriction/engineextension/PhrictionHovercardEngineExtension.php',
'PhrictionInfoConduitAPIMethod' => 'applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php',
'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php',
'PhrictionMarkupPreviewController' => 'applications/phriction/controller/PhrictionMarkupPreviewController.php',
@ -6470,6 +6474,7 @@ phutil_register_library_map(array(
'DifferentialBlockHeraldAction' => 'HeraldAction',
'DifferentialBlockingReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialBranchField' => 'DifferentialCustomField',
'DifferentialBranchFieldTestCase' => 'PhabricatorTestCase',
'DifferentialBuildableEngine' => 'HarbormasterBuildableEngine',
'DifferentialChangeDetailMailView' => 'DifferentialMailView',
'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
@ -6559,7 +6564,7 @@ phutil_register_library_map(array(
'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DifferentialDiffSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DifferentialDiffTestCase' => 'PhutilTestCase',
'DifferentialDiffTestCase' => 'PhabricatorTestCase',
'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction',
'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DifferentialDiffViewController' => 'DifferentialController',
@ -7102,6 +7107,7 @@ phutil_register_library_map(array(
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionServeController' => 'DiffusionController',
'DiffusionServeControllerTestCase' => 'PhabricatorTestCase',
'DiffusionServiceRef' => 'Phobject',
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
'DiffusionSetupException' => 'Exception',
@ -9496,6 +9502,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardColumn' => 'Phobject',
'PhabricatorDashboardConsoleController' => 'PhabricatorDashboardController',
'PhabricatorDashboardController' => 'PhabricatorController',
'PhabricatorDashboardCreateCapability' => 'PhabricatorPolicyCapability',
'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO',
'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType',
'PhabricatorDashboardDatasource' => 'PhabricatorTypeaheadDatasource',
@ -12539,6 +12546,7 @@ phutil_register_library_map(array(
'PhrictionEditEngineController' => 'PhrictionController',
'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionHistoryController' => 'PhrictionController',
'PhrictionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PhrictionInfoConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionListController' => 'PhrictionController',
'PhrictionMarkupPreviewController' => 'PhabricatorController',

View file

@ -30,16 +30,16 @@ abstract class PhabricatorAphlictManagementWorkflow
$full_path = Filesystem::resolvePath($config_file);
$show_path = $full_path;
} else {
$root = dirname(dirname(phutil_get_library_root('phabricator')));
$root = dirname(phutil_get_library_root('phorge'));
$try = array(
'phabricator/conf/aphlict/aphlict.custom.json',
'phabricator/conf/aphlict/aphlict.default.json',
'conf/aphlict/aphlict.custom.json',
'conf/aphlict/aphlict.default.json',
);
foreach ($try as $config) {
$full_path = $root.'/'.$config;
$show_path = $config;
$show_path = '<phorge>/'.$config;
if (Filesystem::pathExists($full_path)) {
break;
}

View file

@ -51,7 +51,7 @@ final class PhabricatorAuthNeedsApprovalController
$viewer,
PhabricatorAuthWaitForApprovalMessageType::MESSAGEKEY);
if (!strlen($text)) {
if (!phutil_nonempty_string($text)) {
return null;
}

View file

@ -150,6 +150,8 @@ final class PhabricatorAuthPasswordEngine
$base_uri = new PhutilURI($base_uri);
$blocklist[] = $base_uri->getDomain();
$blocklist = array_filter($blocklist);
// Generate additional subterms by splitting the raw blocklist on
// characters like "@", " " (space), and "." to break up email addresses,
// readable names, and domain names into components.

View file

@ -264,7 +264,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
'talk to LDAP. Usually you can install it with '.
'`%s`, `%s`, or a similar package manager command.',
'yum install php-ldap',
'apt-get install php5-ldap'));
'apt-get install php-ldap'));
}
}
@ -427,7 +427,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
}
$instruction_text = idx($instructions, $key);
if (strlen($instruction_text)) {
if (phutil_nonempty_string($instruction_text)) {
$form->appendRemarkupInstructions($instruction_text);
}

View file

@ -289,7 +289,7 @@ final class PhabricatorConduitAPIController
);
}
$token_string = idx($metadata, 'token');
$token_string = idx($metadata, 'token', '');
if (strlen($token_string)) {
if (strlen($token_string) != 32) {
@ -683,7 +683,7 @@ final class PhabricatorConduitAPIController
// Otherwise, look for a single parameter called 'params' which has the
// entire param dictionary JSON encoded.
$params_json = $request->getStr('params');
if (strlen($params_json)) {
if (phutil_nonempty_string($params_json)) {
$params = null;
try {
$params = phutil_json_decode($params_json);

View file

@ -83,7 +83,7 @@ final class PhabricatorPHPConfigSetupCheck extends PhabricatorSetupCheck {
// NOTE: We're intentionally telling you to install "mysqlnd" here; on
// Ubuntu, there's no separate "mysqli" package.
phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd'));
phutil_tag('tt', array(), 'sudo apt-get install php-mysqlnd'));
$this->newIssue('php.mysqli')
->setName(pht('MySQLi Extension Not Available'))
@ -103,7 +103,7 @@ final class PhabricatorPHPConfigSetupCheck extends PhabricatorSetupCheck {
'native driver is recommended.'.
"\n\n".
'You may be able to install the native driver with a command like: %s',
phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd'));
phutil_tag('tt', array(), 'sudo apt-get install php-mysqlnd'));
$this->newIssue('php.myqlnd')

View file

@ -189,9 +189,10 @@ final class PhabricatorConfigConsoleController
foreach ($specs as $lib) {
$remote_future = $remote_futures[$lib];
list($err, $stdout) = $remote_future->resolve();
if ($err) {
// If this fails for whatever reason, just move on.
try {
list($stdout, $err) = $remote_future->resolvex();
} catch (CommandException $e) {
$this->logGitErrorWithPotentialTips($e, $lib);
continue;
}
@ -258,13 +259,14 @@ final class PhabricatorConfigConsoleController
$results = array();
foreach ($log_futures as $lib => $future) {
list($err, $stdout) = $future->resolve();
if (!$err) {
try {
list($stdout, $err) = $future->resolvex();
list($hash, $epoch) = explode(' ', $stdout);
} else {
} catch (CommandException $e) {
$hash = null;
$epoch = null;
}
$this->logGitErrorWithPotentialTips($e, $lib);
}
$result = array(
'hash' => $hash,
@ -275,7 +277,7 @@ final class PhabricatorConfigConsoleController
$upstream_future = idx($upstream_futures, $lib);
if ($upstream_future) {
list($err, $stdout) = $upstream_future->resolve();
list($stdout, $err) = $upstream_future->resolvex();
if (!$err) {
$branchpoint = trim($stdout);
if (strlen($branchpoint)) {
@ -340,5 +342,36 @@ final class PhabricatorConfigConsoleController
->appendChild($table_view);
}
/**
* Help in better troubleshooting git errors.
* @param CommandException $e Exception
* @param string $lib Library name involved
*/
private function logGitErrorWithPotentialTips($e, $lib) {
// First, detect this specific error message related to [safe] stuff.
$expected_error_msg_part = 'detected dubious ownership in repository';
$stderr = $e->getStderr();
if (strpos($stderr, $expected_error_msg_part) !== false) {
// Found! Let's show a nice resolution tip.
// Complete path of the problematic repository.
$lib_root = dirname(phutil_get_library_root($lib));
phlog(pht(
"Cannot identify the version of the %s repository because ".
"the webserver does not trust it (more info on Task %s).\n".
"Try this system resolution:\n".
"sudo git config --system --add safe.directory %s",
$lib,
'https://we.phorge.it/T15282',
$lib_root));
} else {
// Otherwise show a generic error message
phlog($e);
}
}
}

View file

@ -227,7 +227,7 @@ EODOC
pht(
'Controls whether email for multiple recipients is sent by '.
'creating one message with everyone in the "To:" line, or '.
'multiple messages that each have a single recipeint in the '.
'multiple messages that each have a single recipient in the '.
'"To:" line.'))
->setDescription($one_mail_per_recipient_desc),
$this->newOption('metamta.can-send-as-user', 'bool', false)

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorDashboardCreateCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'dashboard.create';
public function getCapabilityName() {
return pht('Can Create Dashboards');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to create a dashboard.');
}
}

View file

@ -83,4 +83,12 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
);
}
protected function getCustomCapabilities() {
return array(
PhabricatorDashboardCreateCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_USER,
'caption' => pht('Default create policy for Dashboards.'),
),
);
}
}

View file

@ -42,7 +42,7 @@ final class PhabricatorDashboardAdjustController
}
$column_key = $request->getStr('columnKey');
if (strlen($column_key)) {
if (phutil_nonempty_string($column_key)) {
$columns = $ref_list->getColumns();
if (!isset($columns[$column_key])) {
return new Aphront404Response();
@ -52,7 +52,7 @@ final class PhabricatorDashboardAdjustController
$after_ref = null;
$after_key = $request->getStr('afterKey');
if (strlen($after_key)) {
if (phutil_nonempty_string($after_key)) {
$after_ref = $ref_list->getPanelRef($after_key);
if (!$after_ref) {
return new Aphront404Response();

View file

@ -327,7 +327,7 @@ final class PhabricatorDashboardPanelTabsController
'This is already the last tab. It can not move any farther to '.
'the right.'))
->addCancelButton($cancel_uri);
} else if ((string)head_key($old_config) === $target) {
} else if (!$is_next && (string)head_key($old_config) === $target) {
return $this->newDialog()
->setTitle(pht('Impossible!'))
->appendParagraph(

View file

@ -66,6 +66,11 @@ final class PhabricatorDashboardEditEngine
return $object->getURI();
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
PhabricatorDashboardCreateCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
$layout_options = PhabricatorDashboardLayoutMode::getLayoutModeMap();

View file

@ -273,6 +273,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
->addClass('dashboard-box')
->addSigil('dashboard-panel');
// Allow to style Archived Panels differently.
if ($panel && $panel->getIsArchived()) {
$box->addClass('dashboard-panel-disabled');
}
if ($this->getMovable()) {
$box->addSigil('panel-movable');
}
@ -302,6 +307,16 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$header = $this->addPanelHeaderActions($header);
// If the Panel is Archived, show in edit mode as such.
if ($panel && $panel->getIsArchived()) {
$header->setSubheader(
id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_RED)
->setIcon('fa-ban')
->setName(pht('Archived')));
}
break;
case self::HEADER_MODE_NORMAL:
default:

View file

@ -71,6 +71,13 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
foreach ($column->getPanelRefs() as $panel_ref) {
$panel_phid = $panel_ref->getPanelPHID();
$panel = idx($panels, $panel_phid);
// Do not render Archived panels in view mode.
if ($panel && $panel->getIsArchived() && !$is_editable) {
continue;
}
$panel_engine = id(new PhabricatorDashboardPanelRenderingEngine())
->setViewer($viewer)
->setEnableAsyncRendering(true)
@ -83,7 +90,6 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
->setMovable(true)
->setPanelHandle($handles[$panel_phid]);
$panel = idx($panels, $panel_phid);
if ($panel) {
$panel_engine->setPanel($panel);
}

View file

@ -181,7 +181,7 @@ final class PhabricatorDashboardTabsPanelType
->setIcon('fa-chevron-left')
->setHref($prev_uri)
->setWorkflow(true)
->setDisabled(($prev_key === null) || !$can_edit));
->setDisabled($prev_key === null));
$dropdown_menu->addAction(
id(new PhabricatorActionView())
@ -189,7 +189,7 @@ final class PhabricatorDashboardTabsPanelType
->setIcon('fa-chevron-right')
->setHref($next_uri)
->setWorkflow(true)
->setDisabled(($next_key === null) || !$can_edit));
->setDisabled($next_key === null));
$dropdown_menu->addAction(
id(new PhabricatorActionView())
@ -282,6 +282,8 @@ final class PhabricatorDashboardTabsPanelType
$panel_content = pht('(Invalid Panel)');
}
$is_selected = (string)$idx === (string)$selected;
$content_id = celerity_generate_unique_node_id();
$content[] = phutil_tag(

View file

@ -19,6 +19,13 @@ final class PhabricatorDashboardPanelQuery
return $this;
}
/**
* Whether to get only the Archived (`true`), only the not
* Archived (`false`) or all (`null`). Default to `null` (no filter).
*
* @param null|bool $archived
* @return self
*/
public function withArchived($archived) {
$this->archived = $archived;
return $this;

View file

@ -56,7 +56,7 @@ final class DifferentialGetCommitMessageConduitAPIMethod
// show "Field:" templates for some fields even if they are empty.
$edit_mode = $request->getValue('edit');
$is_any_edit = (bool)strlen($edit_mode);
$is_any_edit = (bool)$edit_mode;
$is_create = ($edit_mode == 'create');
$field_list = DifferentialCommitMessageField::newEnabledFields($viewer);
@ -115,7 +115,7 @@ final class DifferentialGetCommitMessageConduitAPIMethod
$is_title = ($field_key == $key_title);
if (!strlen($value)) {
if ($value === null || $value === '') {
if ($is_template) {
$commit_message[] = $label.': ';
}

View file

@ -451,10 +451,9 @@ final class DifferentialChangesetViewController extends DifferentialController {
continue;
}
$coverage_data = idx($test_coverage, $changeset->getFileName());
if (!strlen($coverage_data)) {
continue;
if (phutil_nonempty_string($coverage_data)) {
$coverage[] = $coverage_data;
}
$coverage[] = $coverage_data;
}
if (!$coverage) {

View file

@ -197,7 +197,6 @@ final class DifferentialDiffViewController extends DifferentialController {
if (empty($revisions[$selected_id])) {
$selected = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withAuthors(array($viewer->getPHID()))
->withIDs(array($selected_id))
->requireCapabilities(
array(

View file

@ -45,7 +45,7 @@ final class DifferentialBranchField
return pht('%s (bookmark)', $bookmark);
} else if (strlen($branch)) {
$onto = $diff->loadTargetBranch();
if (strlen($onto) && ($onto !== $branch)) {
if (phutil_nonempty_string($onto) && ($onto !== $branch)) {
return pht(
'%s (branched from %s)',
$branch,

View file

@ -0,0 +1,39 @@
<?php
final class DifferentialBranchFieldTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
private function getTestDiff() {
$parser = new ArcanistDiffParser();
$raw_diff = <<<EODIFF
diff --git a/src b/src
index 123457..bb216b1 100644
--- a/src
+++ b/src
@@ -1,5 +1,5 @@
Line a
-Line b
+Line 2
Line c
Line d
Line e
EODIFF;
return DifferentialDiff::newFromRawChanges(
PhabricatorUser::getOmnipotentUser(),
$parser->parseDiff($raw_diff));
}
public function testRenderDiffPropertyViewValue() {
$test_object = new DifferentialBranchField();
$diff = $this->getTestDiff();
$diff->setBranch('test');
$this->assertEqual('test',
$test_object->renderDiffPropertyViewValue($diff));
}
}

View file

@ -189,7 +189,7 @@ final class DifferentialRevisionEditEngine
// Don't show the "Author" field when creating a revision using the web
// workflow, since it adds more noise than signal to this workflow.
if ($this->getIsCreate()) {
if ($is_create) {
$author_field->setIsHidden(true);
}
@ -239,6 +239,12 @@ final class DifferentialRevisionEditEngine
->setConduitTypeDescription(pht('New reviewers.'))
->setValue($object->getReviewerPHIDsForEdit());
// Prefill Repository for example when coming from "Attach To".
$repository_phid = $object->getRepositoryPHID();
if ($is_create && !$repository_phid && $diff) {
$repository_phid = $diff->getRepositoryPHID();
}
$fields[] = id(new PhabricatorDatasourceEditField())
->setKey('repositoryPHID')
->setLabel(pht('Repository'))
@ -248,7 +254,7 @@ final class DifferentialRevisionEditEngine
->setDescription(pht('The repository the revision belongs to.'))
->setConduitDescription(pht('Change the repository for this revision.'))
->setConduitTypeDescription(pht('New repository.'))
->setSingleValue($object->getRepositoryPHID());
->setSingleValue($repository_phid);
// This is a little flimsy, but allows "Maniphest Tasks: ..." to continue
// working properly in commit messages until we fully sort out T5873.

View file

@ -60,7 +60,7 @@ abstract class DifferentialCommitMessageField
}
public function renderFieldValue($value) {
if (!strlen($value)) {
if (!phutil_nonempty_string($value)) {
return null;
}

View file

@ -72,7 +72,7 @@ final class DifferentialRevisionIDCommitMessageField
}
public function renderFieldValue($value) {
if (!strlen($value)) {
if (!phutil_nonempty_string($value)) {
return null;
}

View file

@ -28,4 +28,21 @@ final class DifferentialCommitMessageFieldTestCase
unset($env);
}
public function testRenderFieldValue() {
$test_object = new DifferentialRevertPlanCommitMessageField();
$this->assertEqual('foo', $test_object->renderFieldValue('foo'),
'Normal strings should be rendered unaltered');
$this->assertEqual(null, $test_object->renderFieldValue(''),
'Empty strings should be returned as null');
$this->assertEqual(null, $test_object->renderFieldValue(null),
'null values strings should be returned as null');
$test_object = new DifferentialRevisionIDCommitMessageField();
$expected = 'http://phabricator.example.com/D123';
$this->assertEqual($expected, $test_object->renderFieldValue('123'));
$this->assertEqual(null, $test_object->renderFieldValue(null));
}
}

View file

@ -780,7 +780,7 @@ final class DifferentialDiff
$refs = array();
$branch = $this->getBranch();
if (strlen($branch)) {
if (phutil_nonempty_string($branch)) {
$refs[] = array(
'type' => 'branch',
'name' => $branch,
@ -788,7 +788,7 @@ final class DifferentialDiff
}
$onto = $this->loadTargetBranch();
if (strlen($onto)) {
if (phutil_nonempty_string($onto)) {
$refs[] = array(
'type' => 'onto',
'name' => $onto,
@ -796,7 +796,7 @@ final class DifferentialDiff
}
$base = $this->getSourceControlBaseRevision();
if (strlen($base)) {
if ($base !== null && strlen($base)) {
$refs[] = array(
'type' => 'base',
'identifier' => $base,
@ -804,7 +804,7 @@ final class DifferentialDiff
}
$bookmark = $this->getBookmark();
if (strlen($bookmark)) {
if (phutil_nonempty_string($bookmark)) {
$refs[] = array(
'type' => 'bookmark',
'name' => $bookmark,

View file

@ -1,6 +1,12 @@
<?php
final class DifferentialDiffTestCase extends PhutilTestCase {
final class DifferentialDiffTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testDetectCopiedCode() {
$copies = $this->detectCopiesIn('lint_engine.diff');
@ -73,5 +79,35 @@ EODIFF;
$this->assertTrue(true);
}
public function testGetFieldValuesForConduit() {
$parser = new ArcanistDiffParser();
$raw_diff = <<<EODIFF
diff --git a/src b/src
index 123457..bb216b1 100644
--- a/src
+++ b/src
@@ -1,5 +1,5 @@
Line a
-Line b
+Line 2
Line c
Line d
Line e
EODIFF;
$diff = DifferentialDiff::newFromRawChanges(
PhabricatorUser::getOmnipotentUser(),
$parser->parseDiff($raw_diff));
$this->assertTrue(true);
$field_values = $diff->getFieldValuesForConduit();
$this->assertTrue(is_array($field_values));
foreach (['revisionPHID', 'authorPHID', 'repositoryPHID', 'refs']
as $key) {
$this->assertTrue(array_key_exists($key, $field_values));
}
}
}

View file

@ -37,7 +37,7 @@ final class DiffusionBrowseQueryConduitAPIMethod
$repository = $drequest->getRepository();
$path = $request->getValue('path');
if (!strlen($path) || $path === '/') {
if (!phutil_nonempty_string($path) || $path === '/') {
$path = null;
}

View file

@ -47,14 +47,14 @@ final class DiffusionHistoryQueryConduitAPIMethod
$against_hash = $request->getValue('against');
$path = $request->getValue('path');
if (!strlen($path)) {
if (!phutil_nonempty_string($path)) {
$path = null;
}
$offset = $request->getValue('offset');
$limit = $request->getValue('limit');
if (strlen($against_hash)) {
if (phutil_nonempty_string($against_hash)) {
$commit_range = "{$against_hash}..{$commit_hash}";
} else {
$commit_range = $commit_hash;

View file

@ -22,7 +22,7 @@ final class DiffusionBrowseController extends DiffusionController {
// list.
$grep = $request->getStr('grep');
if (strlen($grep)) {
if (phutil_nonempty_string($grep)) {
return $this->browseSearch();
}

View file

@ -27,8 +27,9 @@ final class DiffusionCommitController extends DiffusionController {
// If this page is being accessed via "/source/xyz/commit/...", redirect
// to the canonical URI.
$has_callsign = strlen($request->getURIData('repositoryCallsign'));
$has_id = strlen($request->getURIData('repositoryID'));
$has_callsign =
phutil_nonempty_string($request->getURIData('repositoryCallsign'));
$has_id = phutil_nonempty_string($request->getURIData('repositoryID'));
if (!$has_callsign && !$has_id) {
$canonical_uri = $repository->getCommitURI($commit_identifier);
return id(new AphrontRedirectResponse())
@ -922,7 +923,7 @@ final class DiffusionCommitController extends DiffusionController {
private function linkBugtraq($corpus) {
$url = PhabricatorEnv::getEnvConfig('bugtraq.url');
if (!strlen($url)) {
if (!phutil_nonempty_string($url)) {
return $corpus;
}

View file

@ -50,7 +50,7 @@ final class DiffusionHistoryController extends DiffusionController {
// ancestors appropriately, but this would currrently be prohibitively
// expensive in the general case.
$show_graph = !strlen($drequest->getPath());
$show_graph = !phutil_nonempty_string($drequest->getPath());
if ($show_graph) {
$history_list
->setParents($history_results['parents'])
@ -98,11 +98,10 @@ final class DiffusionHistoryController extends DiffusionController {
$viewer = $this->getViewer();
$repository = $drequest->getRepository();
$no_path = !strlen($drequest->getPath());
if ($no_path) {
$header_text = pht('History');
} else {
if (phutil_nonempty_string($drequest->getPath())) {
$header_text = $this->renderPathLinks($drequest, $mode = 'history');
} else {
$header_text = pht('History');
}
$header = id(new PHUIHeaderView())

View file

@ -183,8 +183,8 @@ final class DiffusionServeController extends DiffusionController {
// won't prompt users who provide a username but no password otherwise.
// See T10797 for discussion.
$have_user = strlen(idx($_SERVER, 'PHP_AUTH_USER'));
$have_pass = strlen(idx($_SERVER, 'PHP_AUTH_PW'));
$have_user = strlen(idx($_SERVER, 'PHP_AUTH_USER', ''));
$have_pass = strlen(idx($_SERVER, 'PHP_AUTH_PW', ''));
if ($have_user && $have_pass) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']);

View file

@ -25,7 +25,7 @@ final class DiffusionTagListController extends DiffusionController {
'offset' => $pager->getOffset(),
);
if (strlen($drequest->getSymbolicCommit())) {
if (phutil_nonempty_string($drequest->getSymbolicCommit())) {
$is_commit = true;
$params['commit'] = $drequest->getSymbolicCommit();
} else {

View file

@ -0,0 +1,21 @@
<?php
final class DiffusionServeControllerTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testHandleRequest() {
$aphront_request = new AphrontRequest('example.com', '/');
$diffusion_serve_controller = new DiffusionServeController();
$diffusion_serve_controller->setRequest($aphront_request);
$result = $diffusion_serve_controller->handleRequest($aphront_request);
$this->assertTrue(true, 'handleRequest did not throw an error');
$this->assertTrue($result instanceof PhabricatorVCSResponse,
'handleRequest() returns PhabricatorVCSResponse object');
}
}

View file

@ -87,7 +87,7 @@ final class DiffusionDocumentRenderingEngine
$ref->setSymbolMetadata($this->getSymbolMetadata());
$coverage = $drequest->loadCoverage();
if (strlen($coverage)) {
if (phutil_nonempty_string($coverage)) {
$ref->addCoverage($coverage);
}
}

View file

@ -25,4 +25,45 @@ final class DiffusionCommitHasRevisionEdgeType extends PhabricatorEdgeType {
'The source commit is associated with the destination revision.');
}
public function getTransactionAddString(
$actor,
$add_count,
$add_edges) {
return pht(
'%s added %s revision(s): %s.',
$actor,
$add_count,
$add_edges);
}
public function getTransactionRemoveString(
$actor,
$rem_count,
$rem_edges) {
return pht(
'%s removed %s revision(s): %s.',
$actor,
$rem_count,
$rem_edges);
}
public function getTransactionEditString(
$actor,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited revision(s), added %s: %s; removed %s: %s.',
$actor,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
}

View file

@ -1133,7 +1133,7 @@ final class DiffusionCommitHookEngine extends Phobject {
->setHookWait(phutil_microseconds_since($hook_start));
$identifier = $this->getRequestIdentifier();
if (strlen($identifier)) {
if ($identifier !== null && strlen($identifier)) {
$event->setRequestIdentifier($identifier);
}

View file

@ -48,6 +48,9 @@ final class DiffusionPathIDQuery extends Phobject {
*/
public static function normalizePath($path) {
// Ensure we have a string, not a null.
$path = coalesce($path, '');
// Normalize to single slashes, e.g. "///" => "/".
$path = preg_replace('@[/]{2,}@', '/', $path);

View file

@ -254,7 +254,7 @@ abstract class DiffusionRequest extends Phobject {
}
public function getPath() {
return $this->path;
return coalesce($this->path, '');
}
public function getLine() {

View file

@ -71,7 +71,7 @@ abstract class DiffusionView extends AphrontView {
$display_name = idx($details, 'name');
unset($details['name']);
if (strlen($display_name)) {
if (phutil_nonempty_string($display_name)) {
$display_name = phutil_tag(
'span',
array(

View file

@ -346,7 +346,7 @@ final class DivinerAtom extends Phobject {
->setContext(idx($dictionary, 'context'))
->setLanguage(idx($dictionary, 'language'))
->setParentHash(idx($dictionary, 'parentHash'))
->setDocblockRaw(idx($dictionary, 'docblockRaw'))
->setDocblockRaw(coalesce(idx($dictionary, 'docblockRaw'), ''))
->setProperties(idx($dictionary, 'properties'));
foreach (idx($dictionary, 'warnings', array()) as $warning) {

View file

@ -50,7 +50,7 @@ final class DivinerMainController extends DivinerController {
$text = pht(
"(NOTE) **Looking for documentation?** ".
"If you're looking for help and information about %s, ".
"you can [[https://secure.phabricator.com/diviner/ | ".
"you can [[https://we.phorge.it/diviner/ | ".
"browse the public %s documentation]] on the live site.\n\n".
"Diviner is the documentation generator used to build this ".
"documentation.\n\n".

View file

@ -31,8 +31,10 @@ final class FileUploadConduitAPIMethod extends FileConduitAPIMethod {
$view_policy = $request->getValue('viewPolicy');
$data = $request->getValue('data_base64');
if (!phutil_nonempty_string($data)) {
throw new Exception(pht('Field "data_base64" must be non-empty.'));
}
$data = $this->decodeBase64($data);
$params = array(
'authorPHID' => $viewer->getPHID(),
'canCDN' => $can_cdn,

View file

@ -20,7 +20,7 @@ final class PhabricatorFileLightboxController
return new Aphront404Response();
}
if (strlen($comment)) {
if (phutil_nonempty_string($comment)) {
$xactions = array();
$xactions[] = id(new PhabricatorFileTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)

View file

@ -287,7 +287,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
// NOTE: Once we receive the first chunk, we'll detect its MIME type and
// update the parent file if a MIME type hasn't been provided. This matters
// for large media files like video.
$mime_type = idx($params, 'mime-type');
$mime_type = idx($params, 'mime-type', '');
if (!strlen($mime_type)) {
$file->setMimeType('application/octet-stream');
}

View file

@ -532,4 +532,11 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
$this->assertEqual(array(), $alternate_c);
}
public function testNewChunkedFile() {
$engine = new PhabricatorTestStorageEngine();
$file = PhabricatorFile::newChunkedFile($engine, 10, []);
$this->assertTrue($file instanceof PhabricatorFile,
pht('newChunkedFile returns a PhabricatorFile'));
}
}

View file

@ -102,7 +102,7 @@ final class HarbormasterBuildUnitMessage
$obj->setDuration((float)idx($dict, 'duration'));
$path = idx($dict, 'path');
if (strlen($path)) {
if ($path !== null && strlen($path)) {
$obj->setProperty('path', $path);
}

View file

@ -717,8 +717,8 @@ final class HeraldTranscriptController extends HeraldController {
->setName(pht('Field Values'))
->setIcon('fa-file-text-o');
$xaction_phids = $this->getTranscriptTransactionPHIDs($xscript);
$has_xactions = (bool)$xaction_phids;
$has_xactions = $xscript->getObjectTranscript()
&& $this->getTranscriptTransactionPHIDs($xscript);
$nav->newLink('xactions')
->setName(pht('Transactions'))

View file

@ -182,7 +182,8 @@ final class PhabricatorMemeEngine extends Phobject {
// changes to the image.
$above_text = $this->getAboveText();
$below_text = $this->getBelowText();
if (!strlen(trim($above_text)) && !strlen(trim($below_text))) {
if (($above_text === null || !phutil_nonempty_string(trim($above_text))) &&
($below_text === null || !phutil_nonempty_string(trim($below_text)))) {
return $template_data;
}

View file

@ -63,6 +63,7 @@ final class PhabricatorMailManagementSendTestWorkflow
array(
'name' => 'type',
'param' => 'message-type',
'default' => PhabricatorMailEmailMessage::MESSAGETYPE,
'help' => pht(
'Send the specified type of message (email, sms, ...).'),
),
@ -74,10 +75,6 @@ final class PhabricatorMailManagementSendTestWorkflow
$viewer = $this->getViewer();
$type = $args->getArg('type');
if (!strlen($type)) {
$type = PhabricatorMailEmailMessage::MESSAGETYPE;
}
$type_map = PhabricatorMailExternalMessage::getAllMessageTypes();
if (!isset($type_map[$type])) {
throw new PhutilArgumentUsageException(

View file

@ -65,7 +65,7 @@ final class PhabricatorMailManagementShowOutboundWorkflow
foreach ($messages as $message_key => $message) {
if ($args->getArg('dump-html')) {
$html_body = $message->getHTMLBody();
if (strlen($html_body)) {
if (phutil_nonempty_string($html_body)) {
$template =
"<!doctype html><html><body>{$html_body}</body></html>";
$console->writeOut("%s\n", $html_body);
@ -188,7 +188,7 @@ final class PhabricatorMailManagementShowOutboundWorkflow
$info[] = null;
$info[] = $this->newSectionHeader(pht('TEXT BODY'));
if (strlen($message->getBody())) {
if (phutil_nonempty_string($message->getBody())) {
$info[] = tsprintf('%B', $message->getBody());
} else {
$info[] = pht('(This message has no text body.)');
@ -203,7 +203,7 @@ final class PhabricatorMailManagementShowOutboundWorkflow
$info[] = null;
$info[] = $this->newSectionHeader(pht('HTML BODY'));
if (strlen($message->getHTMLBody())) {
if (phutil_nonempty_string($message->getHTMLBody())) {
$info[] = $message->getHTMLBody();
$info[] = null;
} else {

View file

@ -92,7 +92,7 @@ final class PhabricatorNotificationServersConfigType
}
$path = idx($spec, 'path');
if ($type == 'admin' && strlen($path)) {
if ($type == 'admin' && phutil_nonempty_string($path)) {
throw $this->newException(
pht(
'Notification server configuration describes an invalid host '.

View file

@ -40,34 +40,41 @@ final class PhabricatorNotificationStatusView extends AphrontTagView {
protected function getTagContent() {
$have = PhabricatorEnv::getEnvConfig('notification.servers');
if ($have) {
$icon = id(new PHUIIconView())
->setIcon('fa-circle-o yellow');
$text = pht('Connecting...');
return phutil_tag(
'span',
array(
'class' => 'connection-status-text '.
'aphlict-connection-status-connecting',
),
array(
$icon,
$text,
));
return $this->buildMessageView(
'aphlict-connection-status-connecting',
'fa-circle-o yellow',
pht('Connecting...'));
} else {
$text = pht('Notification server not enabled');
$icon = id(new PHUIIconView())
->setIcon('fa-circle-o grey');
return phutil_tag(
'span',
array(
'class' => 'connection-status-text '.
'aphlict-connection-status-notenabled',
),
array(
$icon,
$text,
));
return $this->buildMessageView(
'aphlict-connection-status-notenabled',
'fa-circle-o grey',
pht('Notification server not enabled'));
}
}
/**
* Create an icon and a message.
*
* @param string $class_name Raw CSS class name(s) space separated
* @param string $icon_name Icon name
* @param string $text Text to be shown
* @return array
*/
private function buildMessageView($class_name, $icon_name, $text) {
$icon = id(new PHUIIconView())
->setIcon($icon_name);
$message = phutil_tag(
'span',
array(
'class' => 'connection-status-text '.$class_name,
),
$text);
return array(
$icon,
$message,
);
}
}

View file

@ -36,7 +36,7 @@ final class PhabricatorUserLogView extends AphrontView {
$rows = array();
foreach ($logs as $log) {
$session = substr($log->getSession(), 0, 6);
$session = substr(coalesce($log->getSession(), ''), 0, 6);
$actor_phid = $log->getActorPHID();
$user_phid = $log->getUserPHID();

View file

@ -56,9 +56,15 @@ final class PhabricatorUserEmpowerTransaction
'%s empowered this user as an administrator.',
$this->renderAuthor());
} else {
return pht(
'%s defrocked this user.',
$this->renderAuthor());
if (PhabricatorEnv::getEnvConfig('phabricator.serious-business')) {
return pht(
'%s removed the administrator role from this user.',
$this->renderAuthor());
} else {
return pht(
'%s defrocked this user.',
$this->renderAuthor());
}
}
}
@ -70,10 +76,17 @@ final class PhabricatorUserEmpowerTransaction
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s defrocked %s.',
$this->renderAuthor(),
$this->renderObject());
if (PhabricatorEnv::getEnvConfig('phabricator.serious-business')) {
return pht(
'%s removed the administrator role from %s.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s defrocked %s.',
$this->renderAuthor(),
$this->renderObject());
}
}
}

View file

@ -117,6 +117,15 @@ final class PhameBlogViewController extends PhameLiveController {
$about,
));
$page->addHeadItem(phutil_tag(
'link',
array(
'rel' => 'alternate',
'type' => 'application/atom+xml',
'href' => $blog->getFeedURI(),
'title' => $blog->getName(),
)));
return $page;
}

View file

@ -200,6 +200,15 @@ final class PhameBlog extends PhameDAO
return '/phame/blog/manage/'.$this->getID().'/';
}
/**
* Get relative URI of Phame blog feed.
*
* @return string Relative URI of Phame blog feed
*/
public function getFeedURI() {
return '/phame/blog/feed/'.$this->getID().'/';
}
public function getProfileImageURI() {
return $this->getProfileImageFile()->getBestURI();
}

View file

@ -102,6 +102,7 @@ final class PholioInlineController extends PholioController {
->addCancelButton($mock_uri, pht('Close'));
}
$error = null;
if ($request->isFormPost()) {
$v_content = $request->getStr('content');
@ -112,9 +113,13 @@ final class PholioInlineController extends PholioController {
} else if ($inline->getID()) {
$inline->delete();
$dictionary = array();
} else {
$error = pht('Comment cannot be empty.');
}
return id(new AphrontAjaxResponse())->setContent($dictionary);
if ($error === null) {
return id(new AphrontAjaxResponse())->setContent($dictionary);
}
}
switch ($mode) {
@ -151,6 +156,7 @@ final class PholioInlineController extends PholioController {
->setUser($viewer)
->setName('content')
->setLabel(pht('Comment'))
->setError($error)
->setValue($v_content));
return $this->newDialog()

View file

@ -0,0 +1,169 @@
<?php
final class PhrictionHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
const EXTENSIONKEY = 'phriction';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorPhrictionApplication');
}
public function getExtensionName() {
return pht('Wiki Documents');
}
public function canRenderObjectHovercard($object) {
return ($object instanceof PhrictionDocument);
}
public function willRenderHovercards(array $objects) {
return array(
'projects' => $this->getProjectHandlesOfDocuments($objects),
'ancestors' => $this->getAncestorHandlesOfDocuments($objects),
);
}
public function renderHovercard(
PHUIHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data) {
$viewer = $this->getViewer();
$phid = $object->getPHID();
$detail_content = array(
id(new PHUIIconView())->setIcon('fa-book'),
);
$ancestor_handles = $data['ancestors'][$phid];
if ($ancestor_handles) {
foreach ($ancestor_handles as $ancestor_handle) {
$detail_content[] = phutil_tag(
'a',
array(
'href' => $ancestor_handle->getUri(),
),
$ancestor_handle->getName());
$detail_content[] = id(new PHUIIconView())
->setIcon('fa-angle-right')
->addClass('phui-crumb-divider');
}
array_pop($detail_content);
} else {
$detail_content[] = pht('Wiki Document');
}
$project_handles = $data['projects'][$phid];
if ($project_handles) {
$list = id(new PHUIHandleTagListView())
->setHandles($project_handles)
->setSlim(true)
->setShowHovercards(false);
$detail_content[] = $list;
}
$hovercard->setDetail(
phutil_tag(
'div',
array(
'class' => 'phui-hovercard-object-type',
),
$detail_content));
$content = $object->getContent();
if ($content) {
$hovercard->addField(
pht('Last Author'),
$viewer->renderHandle($content->getAuthorPHID()));
$hovercard->addField(
pht('Last Edited'),
phabricator_dual_datetime($content->getDateCreated(), $viewer));
}
}
private function getProjectHandlesOfDocuments($documents) {
$viewer = $this->getViewer();
$project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$project_phids = array();
$project_map = array();
$project_edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($documents, 'getPHID'))
->withEdgeTypes(array($project_edge_type))
->execute();
foreach ($project_edges as $document_phid => $edge_types) {
$document_project_phids = array_keys($edge_types[$project_edge_type]);
$project_map[$document_phid] = array_reverse($document_project_phids);
foreach ($document_project_phids as $project_phid) {
if (!in_array($project_phid, $project_phids, true)) {
$project_phids[] = $project_phid;
}
}
}
if ($project_phids) {
$project_handles = $viewer->loadHandles($project_phids);
$project_handles = iterator_to_array($project_handles);
$project_handles = mpull($project_handles, null, 'getPHID');
foreach ($project_map as $key => $document_project_phids) {
$project_map[$key] = array_select_keys(
$project_handles,
$document_project_phids);
}
}
return $project_map;
}
private function getAncestorHandlesOfDocuments($documents) {
$viewer = $this->getViewer();
$ancestor_slugs = array();
$ancestor_map = array();
foreach ($documents as $document) {
$document_phid = $document->getPHID();
$document_ancestor_slugs = PhabricatorSlug::getAncestry(
$document->getSlug());
$ancestor_map[$document_phid] = $document_ancestor_slugs;
foreach ($document_ancestor_slugs as $slug) {
if (!in_array($slug, $ancestor_slugs, true)) {
$ancestor_slugs[] = $slug;
}
}
}
if ($ancestor_slugs) {
$ancestors = id(new PhrictionDocumentQuery())
->setViewer($viewer)
->withSlugs($ancestor_slugs)
->execute();
$ancestor_phids = mpull($ancestors, 'getPHID', 'getSlug');
$ancestor_handles = $viewer->loadHandles($ancestor_phids);
$ancestor_handles = iterator_to_array($ancestor_handles);
$ancestor_handles = mpull($ancestor_handles, null, 'getPHID');
foreach ($ancestor_map as $key => $document_ancestor_slugs) {
$document_ancestor_phids = array_select_keys(
$ancestor_phids,
$document_ancestor_slugs);
$ancestor_map[$key] = array_select_keys(
$ancestor_handles,
$document_ancestor_phids);
}
}
return $ancestor_map;
}
}

View file

@ -136,7 +136,6 @@ final class PhabricatorProjectColumnHideController
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle($title)
->appendChild($body)
->setDisableWorkflowOnCancel(true)
->addCancelButton($view_uri)
->addSubmitButton($button);

View file

@ -28,6 +28,26 @@ final class PhabricatorProjectHovercardEngineExtension
->execute();
$projects = mpull($projects, null, 'getPHID');
$custom_fields = array();
foreach ($projects as $project) {
$field = PhabricatorCustomField::getObjectField(
$project,
PhabricatorCustomField::ROLE_VIEW,
'std:project:internal:description');
if ($field === null) {
// This means the field is disabled, it would always be null.
break;
}
$field
->setViewer($viewer)
->readValueFromObject($project);
$custom_fields[] = $field;
}
id(new PhabricatorCustomFieldStorageQuery())
->addFields($custom_fields)
->execute();
return array(
'projects' => $projects,
);

View file

@ -271,6 +271,19 @@ final class PhabricatorProjectColumn
pht(
'For columns that proxy another object (like a subproject or '.
'milestone), the PHID of the object they proxy.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isHidden')
->setType('bool')
->setDescription(pht('True if this column is hidden.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isDefaultColumn')
->setType('bool')
->setDescription(pht('True if this is the default column.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('sequence')
->setType('int')
->setDescription(
pht('The sequence in which this column appears on the workboard.')),
);
}
@ -279,6 +292,9 @@ final class PhabricatorProjectColumn
'name' => $this->getDisplayName(),
'proxyPHID' => $this->getProxyPHID(),
'project' => $this->getProject()->getRefForConduit(),
'isHidden' => $this->isHidden(),
'isDefaultColumn' => $this->isDefaultColumn(),
'sequence' => (int)$this->getSequence(),
);
}

View file

@ -68,6 +68,28 @@ final class PhabricatorProjectCardView extends AphrontTagView {
$description = null;
// This getProxy() feels hacky - see also PhabricatorProjectDatasource:67
$description_field = PhabricatorCustomField::getObjectField(
$project,
PhabricatorCustomField::ROLE_VIEW,
'std:project:internal:description');
if ($description_field !== null) {
$description_field = $description_field->getProxy();
$description = $description_field->getFieldValue();
if (phutil_nonempty_string($description)) {
$description = PhabricatorMarkupEngine::summarizeSentence($description);
$description = id(new PHUIRemarkupView($viewer, $description))
->setContextObject($project);
$description = phutil_tag(
'div',
array('class' => 'project-card-body phui-header-shell'),
$description);
}
}
$card = phutil_tag(
'div',
array(

View file

@ -42,7 +42,7 @@ final class PhabricatorRepositoryManagementMaintenanceWorkflow
}
$message = $args->getArg('start');
$is_start = (bool)strlen($message);
$is_start = $message !== null;
$is_stop = $args->getArg('stop');
if (!$is_start && !$is_stop) {

View file

@ -779,15 +779,15 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
break;
case 'compare':
$uri = $this->getPathURI("/{$action}/");
if (strlen($head)) {
if (phutil_nonempty_scalar($head)) {
$query['head'] = $head;
} else if (strlen($raw_commit)) {
} else if (phutil_nonempty_scalar($raw_commit)) {
$query['commit'] = $raw_commit;
} else if (strlen($raw_branch)) {
} else if (phutil_nonempty_scalar($raw_branch)) {
$query['head'] = $raw_branch;
}
if (strlen($against)) {
if (phutil_nonempty_scalar($against)) {
$query['against'] = $against;
}
break;
@ -1160,7 +1160,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
*/
public function getRemoteURIObject() {
$raw_uri = $this->getDetail('remote-uri');
if (!strlen($raw_uri)) {
if (!phutil_nonempty_string($raw_uri)) {
return new PhutilURI('');
}

View file

@ -25,12 +25,12 @@ final class PhabricatorRepositoryCallsignTransaction
$old = $this->getOldValue();
$new = $this->getNewValue();
if (!strlen($old)) {
if ($old === null) {
return pht(
'%s set the callsign for this repository to %s.',
$this->renderAuthor(),
$this->renderNewValue());
} else if (!strlen($new)) {
} else if ($new === null) {
return pht(
'%s removed the callsign (%s) for this repository.',
$this->renderAuthor(),

View file

@ -17,12 +17,12 @@ final class PhabricatorRepositoryEncodingTransaction
$old = $this->getOldValue();
$new = $this->getNewValue();
if (strlen($old) && !strlen($new)) {
if ($old !== null && $new === null) {
return pht(
'%s removed the %s encoding configured for this repository.',
$this->renderAuthor(),
$this->renderOldValue());
} else if (strlen($new) && !strlen($old)) {
} else if ($new !== null && $old === null) {
return pht(
'%s set the encoding for this repository to %s.',
$this->renderAuthor(),

View file

@ -25,11 +25,14 @@ final class PhabricatorRepositoryMaintenanceTransaction
$old = $this->getOldValue();
$new = $this->getNewValue();
if (strlen($old) && !strlen($new)) {
$old_nonempty = phutil_nonempty_string($old);
$new_nonempty = phutil_nonempty_string($new);
if ($old_nonempty && !$new_nonempty) {
return pht(
'%s took this repository out of maintenance mode.',
$this->renderAuthor());
} else if (!strlen($old) && strlen($new)) {
} else if (!$old_nonempty && $new_nonempty) {
return pht(
'%s put this repository into maintenance mode.',
$this->renderAuthor());

View file

@ -17,12 +17,12 @@ final class PhabricatorRepositoryStagingURITransaction
$old = $this->getOldValue();
$new = $this->getNewValue();
if (!strlen($old)) {
if ($old === null || !strlen($old)) {
return pht(
'%s set %s as the staging area for this repository.',
$this->renderAuthor(),
$this->renderNewValue());
} else if (!strlen($new)) {
} else if ($new === null || !strlen($new)) {
return pht(
'%s removed %s as the staging area for this repository.',
$this->renderAuthor(),

View file

@ -13,7 +13,7 @@ final class PhabricatorSearchController
$viewer = $this->getViewer();
$query = $request->getStr('query');
if ($request->getStr('jump') != 'no' && strlen($query)) {
if ($request->getStr('jump') != 'no' && phutil_nonempty_string($query)) {
$jump_uri = id(new PhabricatorDatasourceEngine())
->setViewer($viewer)
->newJumpURI($query);

View file

@ -1033,12 +1033,22 @@ abstract class PhabricatorApplicationTransaction
count($rem),
$this->renderSubscriberList($rem, 'rem'));
} else if ($add) {
if ($this->isSelfSubscription()) {
return pht(
'%s subscribed.',
$this->renderHandleLink($author_phid));
}
return pht(
'%s added %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderSubscriberList($add, 'add'));
} else if ($rem) {
if ($this->isSelfSubscription()) {
return pht(
'%s unsubscribed.',
$this->renderHandleLink($author_phid));
}
return pht(
'%s removed %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),

View file

@ -139,7 +139,7 @@ server.modules list:
Finally, you should run the following commands to enable php support:
$ sudo apt-get install php5-cgi # for Ubuntu; other distros should be similar
$ sudo apt-get install php-cgi # for Ubuntu; other distros should be similar
$ sudo lighty-enable-mod fastcgi-php
Restart lighttpd after making your edits, then continue below.

View file

@ -46,6 +46,62 @@ If you want finer-grained control, you can use:
NOTE: When you upgrade Phorge or change configuration, you should restart
the daemons by running `phd restart`.
Automatically start phd
=======================
NOTE: If you are opinionated against systemd, cover the eyes of your children
right now!1!
Computers are good in automatically starting stuff, thanks to the invention
of the "init system".
Phorge virtually supports any init system. Which one is yours? Don't worry.
If you don't know, it's systemd.
We propose a minimal systemd configuration file, following some assumptions:
- your lovely Phorge is installed `/somewhere`
- you have a database service called `mariadb`
- you have a dedicated Unix user called `daemon-user` - coming from
@{article:Diffusion User Guide}
With the above assumptions, create this configuration file as super-user:
```lang=ini,name=/etc/systemd/system/phorge-phd.service
[Unit]
Description=Phorge Daemons
Documentation=https://we.phorge.it/book/phorge/article/managing_daemons/
After=syslog.target network.target mariadb.service
[Service]
Type=forking
User=daemon-user
Group=daemon-user
ExecStart=/somewhere/phorge/bin/phd start
ExecStop=/somewhere/phorge/bin/phd stop
[Install]
WantedBy=multi-user.target
```
To install this new systemd configuration, execute these commands as
super-user:
```
systemctl daemon-reload
systemctl enable --now phorge-phd
```
Now the process has started and will survive after any reboot.
To check if everything is OK:
```
systemctl status phorge-phd
```
Anything else can be explored in depth by reading the systemd documentation.
= Daemon Console =
You can view status and debugging information for daemons in the Daemon Console

View file

@ -112,5 +112,5 @@ like one of these on your system, or a different command:
```
$ sudo /etc/init.d/php-fpm restart
$ sudo service php5-fpm reload
$ sudo service php-fpm reload
```

View file

@ -123,7 +123,7 @@ Here's a general description of what you need to install:
- MySQL Server (usually "mysqld" or "mysql-server" or "mariadb-server")
- PHP (usually "php")
- Required PHP extensions: mbstring, iconv, mysql (or mysqli), curl, pcntl
(these might be something like "php-mysql" or "php5-mysqlnd")
(these might be something like "php-mysql" or "php-mysqlnd")
- Optional PHP extensions: gd, zip
If you already have LAMP setup, you've probably already got everything you need.
@ -137,6 +137,12 @@ dependencies:
somewhere/ $ git clone https://we.phorge.it/source/arcanist.git
somewhere/ $ git clone https://we.phorge.it/source/phorge.git
After cloning, flag them as safe (to avoid the error //"fatal:
detected dubious ownership in repository at ..."//):
$ sudo git config --system --add safe.directory somewhere/arcanist
$ sudo git config --system --add safe.directory somewhere/phorge
Next Steps
==========

View file

@ -393,7 +393,7 @@ final class PhabricatorDatabaseRef
if ($is_replica) {
$latency = idx($replica_status, 'Seconds_Behind_Master');
if (!strlen($latency)) {
if (!phutil_nonempty_string($latency)) {
$ref->setReplicaStatus(self::REPLICATION_NOT_REPLICATING);
} else {
$latency = (int)$latency;

View file

@ -301,10 +301,14 @@ abstract class PhabricatorStandardCustomField
}
public function renderPropertyViewValue(array $handles) {
if (!strlen($this->getFieldValue())) {
return null;
// If your field needs to render anything more complicated then a string,
// then you should override this method.
$value_str = phutil_string_cast($this->getFieldValue());
if (phutil_nonempty_string($value_str)) {
return $value_str;
}
return $this->getFieldValue();
return null;
}
public function shouldAppearInApplicationSearch() {
@ -389,7 +393,7 @@ abstract class PhabricatorStandardCustomField
if (is_array($value)) {
return empty($value);
}
return !strlen($value);
return $value === null || !strlen($value);
}
public function getApplicationTransactionTitle(

View file

@ -33,7 +33,7 @@ final class PhabricatorStandardCustomFieldInt
}
public function setValueFromStorage($value) {
if (strlen($value)) {
if (phutil_nonempty_scalar($value)) {
$value = (int)$value;
} else {
$value = null;

View file

@ -16,7 +16,7 @@ final class PhabricatorEditorURIEngine
$pattern = $viewer->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
if (!strlen(trim($pattern))) {
if ($pattern === null || trim($pattern) === '') {
return null;
}

View file

@ -3,6 +3,12 @@
final class PhabricatorEditorURIEngineTestCase
extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testPatternParsing() {
$map = array(
'' => array(),
@ -129,4 +135,15 @@ final class PhabricatorEditorURIEngineTestCase
}
}
public function testNewForViewer() {
$phabricator_user = $this->generateNewTestUser();
try {
$engine = PhabricatorEditorURIEngine::newForViewer($phabricator_user);
$this->assertTrue(true, 'newForViewer did not throw an error');
} catch (Throwable $ex) {
$this->assertTrue(false,
'newForViewer threw an exception:'.$ex->getMessage());
}
}
}

View file

@ -74,7 +74,7 @@ function phabricator_form(PhabricatorUser $user, $attributes, $content) {
$body = array();
$http_method = idx($attributes, 'method');
$is_post = (strcasecmp($http_method, 'POST') === 0);
$is_post = $http_method && (strcasecmp($http_method, 'POST') === 0);
$http_action = idx($attributes, 'action');

View file

@ -44,7 +44,18 @@ final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule {
}
public function markupText($text, $children) {
if (preg_match('/^\s*```/', $text)) {
// Header/footer eventually useful to be nice with "flavored markdown".
// When it starts with ```stuff the header is 'stuff' (->language)
// When it ends with stuff``` the footer is 'stuff' (->garbage)
$header_line = null;
$footer_line = null;
$matches = null;
if (preg_match('/^\s*```(.*)/', $text, $matches)) {
if (isset($matches[1])) {
$header_line = $matches[1];
}
// If this is a ```-style block, trim off the backticks and any leading
// blank line.
$text = preg_replace('/^\s*```(\s*\n)?/', '', $text);
@ -52,6 +63,13 @@ final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule {
}
$lines = explode("\n", $text);
// If we have a flavored header, it has sense to look for the footer.
if ($header_line !== null && $lines) {
$footer_line = $lines[last_key($lines)];
}
// Strip final empty lines
while ($lines && !strlen(last($lines))) {
unset($lines[last_key($lines)]);
}
@ -65,20 +83,39 @@ final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule {
$parser = new PhutilSimpleOptions();
$custom = $parser->parse(head($lines));
$valid_options = null;
if ($custom) {
$valid = true;
$valid_options = true;
foreach ($custom as $key => $value) {
if (!array_key_exists($key, $options)) {
$valid = false;
$valid_options = false;
break;
}
}
if ($valid) {
if ($valid_options) {
array_shift($lines);
$options = $custom + $options;
}
}
// Parse flavored markdown strictly to don't eat legitimate Remarkup.
// Proceed only if we tried to parse options and we failed
// (no options also mean no language).
// For example this is not a valid option: ```php
// Proceed only if the footer exists and it is not: blabla```
// Accept only 2 lines or more. First line: header; then content.
if (
$valid_options === false &&
$header_line !== null &&
$footer_line === '' &&
count($lines) > 1
) {
if (self::isKnownLanguageCode($header_line)) {
array_shift($lines);
$options['lang'] = $header_line;
}
}
// Normalize the text back to a 0-level indent.
$min_indent = 80;
foreach ($lines as $line) {
@ -249,4 +286,61 @@ final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule {
$engine->highlightSource($options['lang'], $text)));
}
/**
* Check if a language code can be used in a generic flavored markdown.
* @param string $lang Language code
* @return bool
*/
private static function isKnownLanguageCode($lang) {
$languages = self::knownLanguageCodes();
return isset($languages[$lang]);
}
/**
* Get the available languages for a generic flavored markdown.
* @return array Languages as array keys. Ignore the value.
*/
private static function knownLanguageCodes() {
// This is a friendly subset from https://pygments.org/languages/
static $map = array(
'arduino' => 1,
'assembly' => 1,
'awk' => 1,
'bash' => 1,
'bat' => 1,
'c' => 1,
'cmake' => 1,
'cobol' => 1,
'cpp' => 1,
'css' => 1,
'csharp' => 1,
'dart' => 1,
'delphi' => 1,
'fortran' => 1,
'go' => 1,
'groovy' => 1,
'haskell' => 1,
'java' => 1,
'javascript' => 1,
'kotlin' => 1,
'lisp' => 1,
'lua' => 1,
'matlab' => 1,
'make' => 1,
'perl' => 1,
'php' => 1,
'powershell' => 1,
'python' => 1,
'r' => 1,
'ruby' => 1,
'rust' => 1,
'scala' => 1,
'sh' => 1,
'sql' => 1,
'typescript' => 1,
'vba' => 1,
);
return $map;
}
}

View file

@ -114,7 +114,7 @@ final class PhutilRemarkupTableBlockRule extends PhutilRemarkupBlockRule {
if ($cell->isContentNode()) {
$content = $node->getContent();
if (!strlen(trim($content))) {
if ($content === null || trim($content) === '') {
continue;
}

View file

@ -2,6 +2,8 @@
/**
* Test cases for @{class:PhutilRemarkupEngine}.
* @TODO: This unit is not always triggered when you need it.
* https://we.phorge.it/T15500
*/
final class PhutilRemarkupEngineTestCase extends PhutilTestCase {

View file

@ -0,0 +1,7 @@
```cpp
code
```
~~~~~~~~~~
<div class="remarkup-code-block" data-code-lang="cpp" data-sigil="remarkup-code-block"><pre class="remarkup-code">code</pre></div>
~~~~~~~~~~
code

View file

@ -0,0 +1,18 @@
```#comment
code
#more comment
more code```
~~~~~~~~~~
<div class="remarkup-code-block" data-code-lang="text" data-sigil="remarkup-code-block"><pre class="remarkup-code">#comment
code
#more comment
more code</pre></div>
~~~~~~~~~~
#comment
code
#more comment
more code

View file

@ -0,0 +1,9 @@
```
cpp
second line```
~~~~~~~~~~
<div class="remarkup-code-block" data-code-lang="text" data-sigil="remarkup-code-block"><pre class="remarkup-code">cpp
second line</pre></div>
~~~~~~~~~~
cpp
second line

View file

@ -0,0 +1,20 @@
```cpp
code
more code
more code
```
~~~~~~~~~~
<div class="remarkup-code-block" data-code-lang="cpp" data-sigil="remarkup-code-block"><pre class="remarkup-code">code
more code
more code</pre></div>
~~~~~~~~~~
code
more code
more code

View file

@ -23,6 +23,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
private $crumbs;
private $navigation;
private $footer;
private $headItems = array();
public function setShowFooter($show_footer) {
$this->showFooter = $show_footer;
@ -375,6 +376,18 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
}
/**
* Insert a HTML element into <head> of the page to render.
* Used by PhameBlogViewController.
*
* @param PhutilSafeHTML HTML header to add
*/
public function addHeadItem($html) {
if ($html instanceof PhutilSafeHTML) {
$this->headItems[] = $html;
}
}
protected function getHead() {
$monospaced = null;
@ -407,9 +420,10 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
}
return hsprintf(
'%s%s%s',
'%s%s%s%s',
parent::getHead(),
$font_css,
phutil_implode_html('', $this->headItems),
$response->renderSingleResource('javelin-magical-init', 'phabricator'));
}

Some files were not shown because too many files have changed in this diff Show more