1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-05 13:16:14 +01:00

(stable) Promote 2019 Week 32

This commit is contained in:
epriestley 2019-08-11 09:49:10 -07:00
commit 26f9ba4684
36 changed files with 929 additions and 155 deletions

View file

@ -9,10 +9,10 @@ return array(
'names' => array( 'names' => array(
'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf', 'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'af983028', 'core.pkg.css' => '5a4a5010',
'core.pkg.js' => '73a06a9f', 'core.pkg.js' => '73a06a9f',
'differential.pkg.css' => '8d8360fb', 'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996', 'differential.pkg.js' => '0b037a4f',
'diffusion.pkg.css' => '42c75c37', 'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => 'a98c0bf7', 'diffusion.pkg.js' => 'a98c0bf7',
'maniphest.pkg.css' => '35995d6d', 'maniphest.pkg.css' => '35995d6d',
@ -24,7 +24,7 @@ return array(
'rsrc/audio/basic/ting.mp3' => 'a6b6540e', 'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
'rsrc/css/aphront/aphront-bars.css' => '4a327b4a', 'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
'rsrc/css/aphront/dark-console.css' => '7f06cda2', 'rsrc/css/aphront/dark-console.css' => '7f06cda2',
'rsrc/css/aphront/dialog-view.css' => 'b70c70df', 'rsrc/css/aphront/dialog-view.css' => '874f5c06',
'rsrc/css/aphront/list-filter-view.css' => 'feb64255', 'rsrc/css/aphront/list-filter-view.css' => 'feb64255',
'rsrc/css/aphront/multi-column.css' => 'fbc00ba3', 'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
'rsrc/css/aphront/notification.css' => '30240bd2', 'rsrc/css/aphront/notification.css' => '30240bd2',
@ -484,7 +484,7 @@ return array(
'rsrc/js/core/behavior-line-linker.js' => 'e15c8b1f', 'rsrc/js/core/behavior-line-linker.js' => 'e15c8b1f',
'rsrc/js/core/behavior-linked-container.js' => '74446546', 'rsrc/js/core/behavior-linked-container.js' => '74446546',
'rsrc/js/core/behavior-more.js' => '506aa3f4', 'rsrc/js/core/behavior-more.js' => '506aa3f4',
'rsrc/js/core/behavior-object-selector.js' => 'a4af0b4a', 'rsrc/js/core/behavior-object-selector.js' => '98ef467f',
'rsrc/js/core/behavior-oncopy.js' => 'ff7b3f22', 'rsrc/js/core/behavior-oncopy.js' => 'ff7b3f22',
'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949', 'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f',
@ -530,7 +530,7 @@ return array(
'almanac-css' => '2e050f4f', 'almanac-css' => '2e050f4f',
'aphront-bars' => '4a327b4a', 'aphront-bars' => '4a327b4a',
'aphront-dark-console-css' => '7f06cda2', 'aphront-dark-console-css' => '7f06cda2',
'aphront-dialog-view-css' => 'b70c70df', 'aphront-dialog-view-css' => '874f5c06',
'aphront-list-filter-view-css' => 'feb64255', 'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3', 'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46', 'aphront-panel-view-css' => '46923d46',
@ -645,7 +645,7 @@ return array(
'javelin-behavior-phabricator-line-linker' => 'e15c8b1f', 'javelin-behavior-phabricator-line-linker' => 'e15c8b1f',
'javelin-behavior-phabricator-nav' => 'f166c949', 'javelin-behavior-phabricator-nav' => 'f166c949',
'javelin-behavior-phabricator-notification-example' => '29819b75', 'javelin-behavior-phabricator-notification-example' => '29819b75',
'javelin-behavior-phabricator-object-selector' => 'a4af0b4a', 'javelin-behavior-phabricator-object-selector' => '98ef467f',
'javelin-behavior-phabricator-oncopy' => 'ff7b3f22', 'javelin-behavior-phabricator-oncopy' => 'ff7b3f22',
'javelin-behavior-phabricator-remarkup-assist' => '2f80333f', 'javelin-behavior-phabricator-remarkup-assist' => '2f80333f',
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6', 'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
@ -1730,6 +1730,12 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-router', 'javelin-router',
), ),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'9aae2b66' => array( '9aae2b66' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@ -1790,12 +1796,6 @@ return array(
'phui-button-css', 'phui-button-css',
'phui-button-simple-css', 'phui-button-simple-css',
), ),
'a4af0b4a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'a5257c4e' => array( 'a5257c4e' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',

View file

@ -3878,7 +3878,21 @@ phutil_register_library_map(array(
'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php', 'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php',
'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php', 'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php',
'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php', 'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php',
'PhabricatorPDFCatalogObject' => 'applications/phortune/pdf/PhabricatorPDFCatalogObject.php',
'PhabricatorPDFContentsObject' => 'applications/phortune/pdf/PhabricatorPDFContentsObject.php',
'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php', 'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php',
'PhabricatorPDFFontObject' => 'applications/phortune/pdf/PhabricatorPDFFontObject.php',
'PhabricatorPDFFragment' => 'applications/phortune/pdf/PhabricatorPDFFragment.php',
'PhabricatorPDFFragmentOffset' => 'applications/phortune/pdf/PhabricatorPDFFragmentOffset.php',
'PhabricatorPDFGenerator' => 'applications/phortune/pdf/PhabricatorPDFGenerator.php',
'PhabricatorPDFHeadFragment' => 'applications/phortune/pdf/PhabricatorPDFHeadFragment.php',
'PhabricatorPDFInfoObject' => 'applications/phortune/pdf/PhabricatorPDFInfoObject.php',
'PhabricatorPDFIterator' => 'applications/phortune/pdf/PhabricatorPDFIterator.php',
'PhabricatorPDFObject' => 'applications/phortune/pdf/PhabricatorPDFObject.php',
'PhabricatorPDFPageObject' => 'applications/phortune/pdf/PhabricatorPDFPageObject.php',
'PhabricatorPDFPagesObject' => 'applications/phortune/pdf/PhabricatorPDFPagesObject.php',
'PhabricatorPDFResourcesObject' => 'applications/phortune/pdf/PhabricatorPDFResourcesObject.php',
'PhabricatorPDFTailFragment' => 'applications/phortune/pdf/PhabricatorPDFTailFragment.php',
'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php',
'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
@ -10101,7 +10115,24 @@ phutil_register_library_map(array(
'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField', 'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorPDFCatalogObject' => 'PhabricatorPDFObject',
'PhabricatorPDFContentsObject' => 'PhabricatorPDFObject',
'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine', 'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorPDFFontObject' => 'PhabricatorPDFObject',
'PhabricatorPDFFragment' => 'Phobject',
'PhabricatorPDFFragmentOffset' => 'Phobject',
'PhabricatorPDFGenerator' => 'Phobject',
'PhabricatorPDFHeadFragment' => 'PhabricatorPDFFragment',
'PhabricatorPDFInfoObject' => 'PhabricatorPDFObject',
'PhabricatorPDFIterator' => array(
'Phobject',
'Iterator',
),
'PhabricatorPDFObject' => 'PhabricatorPDFFragment',
'PhabricatorPDFPageObject' => 'PhabricatorPDFObject',
'PhabricatorPDFPagesObject' => 'PhabricatorPDFObject',
'PhabricatorPDFResourcesObject' => 'PhabricatorPDFObject',
'PhabricatorPDFTailFragment' => 'PhabricatorPDFFragment',
'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPHID' => 'Phobject', 'PhabricatorPHID' => 'Phobject',
'PhabricatorPHIDConstants' => 'Phobject', 'PhabricatorPHIDConstants' => 'Phobject',

View file

@ -194,6 +194,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$control = id(new PHUIFormNumberControl()) $control = id(new PHUIFormNumberControl())
->setName($name) ->setName($name)
->setDisableAutocomplete(true) ->setDisableAutocomplete(true)
->setAutofocus(true)
->setValue($value) ->setValue($value)
->setError($error); ->setError($error);
} }

View file

@ -3,8 +3,24 @@
final class ConduitEpochParameterType final class ConduitEpochParameterType
extends ConduitParameterType { extends ConduitParameterType {
private $allowNull;
public function setAllowNull($allow_null) {
$this->allowNull = $allow_null;
return $this;
}
public function getAllowNull() {
return $this->allowNull;
}
protected function getParameterValue(array $request, $key, $strict) { protected function getParameterValue(array $request, $key, $strict) {
$value = parent::getParameterValue($request, $key, $strict); $value = parent::getParameterValue($request, $key, $strict);
if ($this->allowNull && ($value === null)) {
return $value;
}
$value = $this->parseIntValue($request, $key, $value, $strict); $value = $this->parseIntValue($request, $key, $value, $strict);
if ($value <= 0) { if ($value <= 0) {

View file

@ -17,32 +17,31 @@ final class DiffusionRepositoryEditDeleteController
->setRepository($repository) ->setRepository($repository)
->getPanelURI(); ->getPanelURI();
$dialog = new AphrontDialogView(); $doc_uri = PhabricatorEnv::getDoclink(
$text_1 = pht( 'Permanently Destroying Data');
'If you really want to delete the repository, run this command from '.
'the command line:');
$command = csprintf(
'phabricator/ $ ./bin/remove destroy %R',
$repository->getMonogram());
$text_2 = pht(
'Repositories touch many objects and as such deletes are '.
'prohibitively expensive to run from the web UI.');
$body = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
array(
phutil_tag('p', array(), $text_1),
phutil_tag('p', array(),
phutil_tag('tt', array(), $command)),
phutil_tag('p', array(), $text_2),
));
return $this->newDialog() return $this->newDialog()
->setTitle(pht('Really want to delete the repository?')) ->setTitle(pht('Delete Repository'))
->appendChild($body) ->appendParagraph(
->addCancelButton($panel_uri, pht('Okay')); pht(
'To permanently destroy this repository, run this command from '.
'the command line:'))
->appendCommand(
csprintf(
'phabricator/ $ ./bin/remove destroy %R',
$repository->getMonogram()))
->appendParagraph(
pht(
'Repositories can not be permanently destroyed from the web '.
'interface. See %s in the documentation for more information.',
phutil_tag(
'a',
array(
'href' => $doc_uri,
'target' => '_blank',
),
pht('Permanently Destroying Data'))))
->addCancelButton($panel_uri, pht('Close'));
} }
} }

View file

@ -155,8 +155,6 @@ final class DiffusionRepositoryBasicsManagementPanel
->setName(pht('Delete Repository')) ->setName(pht('Delete Repository'))
->setHref($delete_uri) ->setHref($delete_uri)
->setIcon('fa-times') ->setIcon('fa-times')
->setColor(PhabricatorActionView::RED)
->setDisabled(true)
->setWorkflow(true)); ->setWorkflow(true));
return $this->newCurtainView() return $this->newCurtainView()

View file

@ -47,7 +47,7 @@ final class DiffusionPatternSearchView extends DiffusionView {
$offset = $match[1]; $offset = $match[1];
if ($cursor != $offset) { if ($cursor != $offset) {
$output[] = array( $output[] = array(
'text' => substr($string, $cursor, $offset), 'text' => substr($string, $cursor, ($offset - $cursor)),
'highlight' => false, 'highlight' => false,
); );
} }

View file

@ -451,15 +451,20 @@ You can choose the default priority for newly created tasks with
EOTEXT EOTEXT
)); ));
$fields_description = $this->deformat(pht(<<<EOTEXT
List of custom fields for Maniphest tasks.
For details on adding custom fields to Maniphest, see [[ %s | %s ]] in the
documentation.
EOTEXT
,
PhabricatorEnv::getDoclink('Configuring Custom Fields'),
pht('Configuring Custom Fields')));
return array( return array(
$this->newOption('maniphest.custom-field-definitions', 'wild', array()) $this->newOption('maniphest.custom-field-definitions', 'wild', array())
->setSummary(pht('Custom Maniphest fields.')) ->setSummary(pht('Custom Maniphest fields.'))
->setDescription( ->setDescription($fields_description)
pht(
'Array of custom fields for Maniphest tasks. For details on '.
'adding custom fields to Maniphest, see "Configuring Custom '.
'Fields" in the documentation.'))
->addExample($fields_json, pht('Valid setting')), ->addExample($fields_json, pht('Valid setting')),
$this->newOption('maniphest.fields', $custom_field_type, $default_fields) $this->newOption('maniphest.fields', $custom_field_type, $default_fields)
->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass()) ->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass())

View file

@ -17,58 +17,35 @@ final class PhabricatorPeopleDeleteController
$manage_uri = $this->getApplicationURI("manage/{$id}/"); $manage_uri = $this->getApplicationURI("manage/{$id}/");
if ($user->getPHID() == $viewer->getPHID()) { $doc_uri = PhabricatorEnv::getDoclink(
return $this->buildDeleteSelfResponse($manage_uri); 'Permanently Destroying Data');
}
$str1 = pht(
'Be careful when deleting users! This will permanently and '.
'irreversibly destroy this user account.');
$str2 = pht(
'If this user interacted with anything, it is generally better to '.
'disable them, not delete them. If you delete them, it will no longer '.
'be possible to (for example) search for objects they created, and you '.
'will lose other information about their history. Disabling them '.
'instead will prevent them from logging in, but will not destroy any of '.
'their data.');
$str3 = pht(
'It is generally safe to delete newly created users (and test users and '.
'so on), but less safe to delete established users. If possible, '.
'disable them instead.');
$str4 = pht('To permanently destroy this user, run this command:');
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
csprintf(
" phabricator/ $ ./bin/remove destroy %R\n",
'@'.$user->getUsername()));
return $this->newDialog() return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle(pht('Delete User'))
->setTitle(pht('Permanently Delete User')) ->appendParagraph(
->setShortTitle(pht('Delete User')) pht(
->appendParagraph($str1) 'To permanently destroy this user, run this command from the '.
->appendParagraph($str2) 'command line:'))
->appendParagraph($str3) ->appendCommand(
->appendParagraph($str4) csprintf(
->appendChild($form->buildLayoutView()) 'phabricator/ $ ./bin/remove destroy %R',
$user->getMonogram()))
->appendParagraph(
pht(
'Unless you have a very good reason to delete this user, consider '.
'disabling them instead.'))
->appendParagraph(
pht(
'Users can not be permanently destroyed from the web interface. '.
'See %s in the documentation for more information.',
phutil_tag(
'a',
array(
'href' => $doc_uri,
'target' => '_blank',
),
pht('Permanently Destroying Data'))))
->addCancelButton($manage_uri, pht('Close')); ->addCancelButton($manage_uri, pht('Close'));
} }
private function buildDeleteSelfResponse($cancel_uri) {
return $this->newDialog()
->setTitle(pht('You Shall Journey No Farther'))
->appendParagraph(
pht(
'As you stare into the gaping maw of the abyss, something '.
'holds you back.'))
->appendParagraph(pht('You can not delete your own account.'))
->addCancelButton($cancel_uri, pht('Turn Back'));
}
} }

View file

@ -34,20 +34,6 @@ final class PhabricatorPeopleProfileBadgesController
$user, $user,
PhabricatorPeopleProfileMenuEngine::ITEM_BADGES); PhabricatorPeopleProfileMenuEngine::ITEM_BADGES);
// Best option?
$badges = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withStatuses(array(
PhabricatorBadgesBadge::STATUS_ACTIVE,
))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->setLimit(1)
->execute();
$button = id(new PHUIButtonView()) $button = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
->setIcon('fa-plus') ->setIcon('fa-plus')
@ -55,17 +41,16 @@ final class PhabricatorPeopleProfileBadgesController
->setWorkflow(true) ->setWorkflow(true)
->setHref('/badges/award/'.$user->getID().'/'); ->setHref('/badges/award/'.$user->getID().'/');
if ($badges) { $header->addActionLink($button);
$header->addActionLink($button);
}
$view = id(new PHUITwoColumnView()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setHeader($header)
->addClass('project-view-home') ->addClass('project-view-home')
->addClass('project-view-people-home') ->addClass('project-view-people-home')
->setFooter(array( ->setFooter(
$this->buildBadgesView($user) array(
)); $badges,
));
return $this->newPage() return $this->newPage()
->setTitle($title) ->setTitle($title)

View file

@ -188,8 +188,8 @@ final class PhortuneCartEditor
protected function getMailTo(PhabricatorLiskDAO $object) { protected function getMailTo(PhabricatorLiskDAO $object) {
$phids = array(); $phids = array();
// Reload the cart to pull merchant and account information, in case we // Reload the cart to pull account information, in case we just created the
// just created the object. // object.
$cart = id(new PhortuneCartQuery()) $cart = id(new PhortuneCartQuery())
->setViewer($this->requireActor()) ->setViewer($this->requireActor())
->withPHIDs(array($object->getPHID())) ->withPHIDs(array($object->getPHID()))
@ -199,10 +199,6 @@ final class PhortuneCartEditor
$phids[] = $account_member; $phids[] = $account_member;
} }
foreach ($cart->getMerchant()->getMemberPHIDs() as $merchant_member) {
$phids[] = $merchant_member;
}
return $phids; return $phids;
} }

View file

@ -0,0 +1,26 @@
<?php
final class PhabricatorPDFCatalogObject
extends PhabricatorPDFObject {
private $pagesObject;
public function setPagesObject(PhabricatorPDFPagesObject $pages_object) {
$this->pagesObject = $this->newChildObject($pages_object);
return $this;
}
public function getPagesObject() {
return $this->pagesObject;
}
protected function writeObject() {
$this->writeLine('/Type /Catalog');
$pages_object = $this->getPagesObject();
if ($pages_object) {
$this->writeLine('/Pages %d 0 R', $pages_object->getObjectIndex());
}
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorPDFContentsObject
extends PhabricatorPDFObject {
private $rawContent;
public function setRawContent($raw_content) {
$this->rawContent = $raw_content;
return $this;
}
public function getRawContent() {
return $this->rawContent;
}
protected function writeObject() {
$data = $this->getRawContent();
$stream_length = $this->newStream($data);
$this->writeLine('/Filter /FlateDecode /Length %d', $stream_length);
}
}

View file

@ -0,0 +1,14 @@
<?php
final class PhabricatorPDFFontObject
extends PhabricatorPDFObject {
protected function writeObject() {
$this->writeLine('/Type /Font');
$this->writeLine('/BaseFont /Helvetica-Bold');
$this->writeLine('/Subtype /Type1');
$this->writeLine('/Encoding /WinAnsiEncoding');
}
}

View file

@ -0,0 +1,38 @@
<?php
abstract class PhabricatorPDFFragment
extends Phobject {
private $rope;
public function getAsBytes() {
$this->rope = new PhutilRope();
$this->writeFragment();
$rope = $this->rope;
$this->rope = null;
return $rope->getAsString();
}
public function hasRefTableEntry() {
return false;
}
abstract protected function writeFragment();
final protected function writeLine($pattern) {
$pattern = $pattern."\n";
$argv = func_get_args();
$argv[0] = $pattern;
$line = call_user_func_array('sprintf', $argv);
$this->rope->append($line);
return $this;
}
}

View file

@ -0,0 +1,27 @@
<?php
final class PhabricatorPDFFragmentOffset
extends Phobject {
private $fragment;
private $offset;
public function setFragment(PhabricatorPDFFragment $fragment) {
$this->fragment = $fragment;
return $this;
}
public function getFragment() {
return $this->fragment;
}
public function setOffset($offset) {
$this->offset = $offset;
return $this;
}
public function getOffset() {
return $this->offset;
}
}

View file

@ -0,0 +1,59 @@
<?php
final class PhabricatorPDFGenerator
extends Phobject {
private $objects = array();
private $hasIterator = false;
private $infoObject;
private $catalogObject;
public function addObject(PhabricatorPDFObject $object) {
if ($this->hasIterator) {
throw new Exception(
pht(
'This generator has already emitted an iterator. You can not '.
'modify the PDF document after you begin writing it.'));
}
$this->objects[] = $object;
$index = count($this->objects);
$object->setGenerator($this, $index);
return $this;
}
public function getObjects() {
return $this->objects;
}
public function newIterator() {
$this->hasIterator = true;
return id(new PhabricatorPDFIterator())
->setGenerator($this);
}
public function setInfoObject(PhabricatorPDFInfoObject $info_object) {
$this->addObject($info_object);
$this->infoObject = $info_object;
return $this;
}
public function getInfoObject() {
return $this->infoObject;
}
public function setCatalogObject(
PhabricatorPDFCatalogObject $catalog_object) {
$this->addObject($catalog_object);
$this->catalogObject = $catalog_object;
return $this;
}
public function getCatalogObject() {
return $this->catalogObject;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorPDFHeadFragment
extends PhabricatorPDFFragment {
protected function writeFragment() {
$this->writeLine('%s', '%PDF-1.3');
}
}

View file

@ -0,0 +1,11 @@
<?php
final class PhabricatorPDFInfoObject
extends PhabricatorPDFObject {
final protected function writeObject() {
$this->writeLine('/Producer (Phabricator 20190801)');
$this->writeLine('/CreationDate (D:%s)', date('YmdHis'));
}
}

View file

@ -0,0 +1,103 @@
<?php
final class PhabricatorPDFIterator
extends Phobject
implements Iterator {
private $generator;
private $hasRewound;
private $fragments;
private $fragmentKey;
private $fragmentBytes;
private $fragmentOffsets = array();
private $byteLength;
public function setGenerator(PhabricatorPDFGenerator $generator) {
if ($this->generator) {
throw new Exception(
pht(
'This iterator already has a generator. You can not modify the '.
'generator for a given iterator.'));
}
$this->generator = $generator;
return $this;
}
public function getGenerator() {
if (!$this->generator) {
throw new Exception(
pht(
'This PDF iterator has no associated PDF generator.'));
}
return $this->generator;
}
public function getFragmentOffsets() {
return $this->fragmentOffsets;
}
public function current() {
return $this->fragmentBytes;
}
public function key() {
return $this->framgentKey;
}
public function next() {
$this->fragmentKey++;
if (!$this->valid()) {
return;
}
$fragment = $this->fragments[$this->fragmentKey];
$this->fragmentOffsets[] = id(new PhabricatorPDFFragmentOffset())
->setFragment($fragment)
->setOffset($this->byteLength);
$bytes = $fragment->getAsBytes();
$this->fragmentBytes = $bytes;
$this->byteLength += strlen($bytes);
}
public function rewind() {
if ($this->hasRewound) {
throw new Exception(
pht(
'PDF iterators may not be rewound. Create a new iterator to emit '.
'another PDF.'));
}
$generator = $this->getGenerator();
$objects = $generator->getObjects();
$this->fragments = array();
$this->fragments[] = new PhabricatorPDFHeadFragment();
foreach ($objects as $object) {
$this->fragments[] = $object;
}
$this->fragments[] = id(new PhabricatorPDFTailFragment())
->setIterator($this);
$this->hasRewound = true;
$this->fragmentKey = -1;
$this->byteLength = 0;
$this->next();
}
public function valid() {
return isset($this->fragments[$this->fragmentKey]);
}
}

View file

@ -0,0 +1,95 @@
<?php
abstract class PhabricatorPDFObject
extends PhabricatorPDFFragment {
private $generator;
private $objectIndex;
private $children = array();
private $streams = array();
final public function hasRefTableEntry() {
return true;
}
final protected function writeFragment() {
$this->writeLine('%d 0 obj', $this->getObjectIndex());
$this->writeLine('<<');
$this->writeObject();
$this->writeLine('>>');
$streams = $this->streams;
$this->streams = array();
foreach ($streams as $stream) {
$this->writeLine('stream');
$this->writeLine('%s', $stream);
$this->writeLine('endstream');
}
$this->writeLine('endobj');
}
final public function setGenerator(
PhabricatorPDFGenerator $generator,
$index) {
if ($this->getGenerator()) {
throw new Exception(
pht(
'This PDF object is already registered with a PDF generator. You '.
'can not register an object with more than one generator.'));
}
$this->generator = $generator;
$this->objectIndex = $index;
foreach ($this->getChildren() as $child) {
$generator->addObject($child);
}
return $this;
}
final public function getGenerator() {
return $this->generator;
}
final public function getObjectIndex() {
if (!$this->objectIndex) {
throw new Exception(
pht(
'Trying to get index for object ("%s") which has not been '.
'registered with a generator.',
get_class($this)));
}
return $this->objectIndex;
}
final protected function newChildObject(PhabricatorPDFObject $object) {
if ($this->generator) {
throw new Exception(
pht(
'Trying to add a new PDF Object child after already registering '.
'the object with a generator.'));
}
$this->children[] = $object;
return $object;
}
private function getChildren() {
return $this->children;
}
abstract protected function writeObject();
final protected function newStream($raw_data) {
$stream_data = gzcompress($raw_data);
$this->streams[] = $stream_data;
return strlen($stream_data);
}
}

View file

@ -0,0 +1,48 @@
<?php
final class PhabricatorPDFPageObject
extends PhabricatorPDFObject {
private $pagesObject;
private $contentsObject;
private $resourcesObject;
public function setPagesObject(PhabricatorPDFPagesObject $pages) {
$this->pagesObject = $pages;
return $this;
}
public function setContentsObject(PhabricatorPDFContentsObject $contents) {
$this->contentsObject = $this->newChildObject($contents);
return $this;
}
public function setResourcesObject(PhabricatorPDFResourcesObject $resources) {
$this->resourcesObject = $this->newChildObject($resources);
return $this;
}
protected function writeObject() {
$this->writeLine('/Type /Page');
$pages_object = $this->pagesObject;
$contents_object = $this->contentsObject;
$resources_object = $this->resourcesObject;
if ($pages_object) {
$pages_index = $pages_object->getObjectIndex();
$this->writeLine('/Parent %d 0 R', $pages_index);
}
if ($contents_object) {
$contents_index = $contents_object->getObjectIndex();
$this->writeLine('/Contents %d 0 R', $contents_index);
}
if ($resources_object) {
$resources_index = $resources_object->getObjectIndex();
$this->writeLine('/Resources %d 0 R', $resources_index);
}
}
}

View file

@ -0,0 +1,38 @@
<?php
final class PhabricatorPDFPagesObject
extends PhabricatorPDFObject {
private $pageObjects = array();
public function addPageObject(PhabricatorPDFPageObject $page) {
$page->setPagesObject($this);
$this->pageObjects[] = $this->newChildObject($page);
return $this;
}
public function getPageObjects() {
return $this->pageObjects;
}
protected function writeObject() {
$this->writeLine('/Type /Pages');
$page_objects = $this->getPageObjects();
$this->writeLine('/Count %d', count($page_objects));
$this->writeLine('/MediaBox [%d %d %0.2f %0.2f]', 0, 0, 595.28, 841.89);
if ($page_objects) {
$kids = array();
foreach ($page_objects as $page_object) {
$kids[] = sprintf(
'%d 0 R',
$page_object->getObjectIndex());
}
$this->writeLine('/Kids [%s]', implode(' ', $kids));
}
}
}

View file

@ -0,0 +1,28 @@
<?php
final class PhabricatorPDFResourcesObject
extends PhabricatorPDFObject {
private $fontObjects = array();
public function addFontObject(PhabricatorPDFFontObject $font) {
$this->fontObjects[] = $this->newChildObject($font);
return $this;
}
public function getFontObjects() {
return $this->fontObjects;
}
protected function writeObject() {
$this->writeLine('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
$fonts = $this->getFontObjects();
foreach ($fonts as $font) {
$this->writeLine('/Font <<');
$this->writeLine('/F%d %d 0 R', 1, $font->getObjectIndex());
$this->writeLine('>>');
}
}
}

View file

@ -0,0 +1,72 @@
<?php
final class PhabricatorPDFTailFragment
extends PhabricatorPDFFragment {
private $iterator;
public function setIterator(PhabricatorPDFIterator $iterator) {
$this->iterator = $iterator;
return $this;
}
public function getIterator() {
return $this->iterator;
}
protected function writeFragment() {
$iterator = $this->getIterator();
$generator = $iterator->getGenerator();
$objects = $generator->getObjects();
$xref_offset = null;
$this->writeLine('xref');
$this->writeLine('0 %d', count($objects) + 1);
$this->writeLine('%010d %05d f ', 0, 0xFFFF);
$offset_map = array();
$fragment_offsets = $iterator->getFragmentOffsets();
foreach ($fragment_offsets as $fragment_offset) {
$fragment = $fragment_offset->getFragment();
$offset = $fragment_offset->getOffset();
if ($fragment === $this) {
$xref_offset = $offset;
}
if (!$fragment->hasRefTableEntry()) {
continue;
}
$offset_map[$fragment->getObjectIndex()] = $offset;
}
ksort($offset_map);
foreach ($offset_map as $offset) {
$this->writeLine('%010d %05d n ', $offset, 0);
}
$this->writeLine('trailer');
$this->writeLine('<<');
$this->writeLine('/Size %d', count($objects) + 1);
$info_object = $generator->getInfoObject();
if ($info_object) {
$this->writeLine('/Info %d 0 R', $info_object->getObjectIndex());
}
$catalog_object = $generator->getCatalogObject();
if ($catalog_object) {
$this->writeLine('/Root %d 0 R', $catalog_object->getObjectIndex());
}
$this->writeLine('>>');
$this->writeLine('startxref');
$this->writeLine('%d', $xref_offset);
$this->writeLine('%s', '%%EOF');
}
}

View file

@ -107,7 +107,16 @@ final class PhabricatorProjectColumnBulkMoveController
->executeLayout(); ->executeLayout();
$dst_columns = $layout_engine->getColumns($dst_project->getPHID()); $dst_columns = $layout_engine->getColumns($dst_project->getPHID());
$dst_columns = mpull($columns, null, 'getPHID'); $dst_columns = mpull($dst_columns, null, 'getPHID');
// Prevent moves to milestones or subprojects by selecting their
// columns, since the implications aren't obvious and this doesn't
// work the same way as normal column moves.
foreach ($dst_columns as $key => $dst_column) {
if ($dst_column->getProxyPHID()) {
unset($dst_columns[$key]);
}
}
$has_column = false; $has_column = false;
$dst_column = null; $dst_column = null;
@ -210,12 +219,40 @@ final class PhabricatorProjectColumnBulkMoveController
->setValue($dst_project->getDisplayName())); ->setValue($dst_project->getDisplayName()));
} }
$column_options = array(
'visible' => array(),
'hidden' => array(),
);
$any_hidden = false;
foreach ($dst_columns as $column) {
if (!$column->isHidden()) {
$group = 'visible';
} else {
$group = 'hidden';
}
$phid = $column->getPHID();
$display_name = $column->getDisplayName();
$column_options[$group][$phid] = $display_name;
}
if ($column_options['hidden']) {
$column_options = array(
pht('Visible Columns') => $column_options['visible'],
pht('Hidden Columns') => $column_options['hidden'],
);
} else {
$column_options = $column_options['visible'];
}
$form->appendControl( $form->appendControl(
id(new AphrontFormSelectControl()) id(new AphrontFormSelectControl())
->setName('dstColumnPHID') ->setName('dstColumnPHID')
->setLabel(pht('Move to Column')) ->setLabel(pht('Move to Column'))
->setValue($dst_column_phid) ->setValue($dst_column_phid)
->setOptions(mpull($dst_columns, 'getDisplayName', 'getPHID'))); ->setOptions($column_options));
$submit = pht('Move Tasks'); $submit = pht('Move Tasks');

View file

@ -78,14 +78,29 @@ final class PhabricatorProjectProfileController
$project, $project,
PhabricatorProject::ITEM_PROFILE); PhabricatorProject::ITEM_PROFILE);
$stories = id(new PhabricatorFeedQuery()) $query = id(new PhabricatorFeedQuery())
->setViewer($viewer) ->setViewer($viewer)
->withFilterPHIDs( ->withFilterPHIDs(array($project->getPHID()))
array(
$project->getPHID(),
))
->setLimit(50) ->setLimit(50)
->execute(); ->setReturnPartialResultsOnOverheat(true);
$stories = $query->execute();
$overheated_view = null;
$is_overheated = $query->getIsOverheated();
if ($is_overheated) {
$overheated_message =
PhabricatorApplicationSearchController::newOverheatedError(
(bool)$stories);
$overheated_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setTitle(pht('Query Overheated'))
->setErrors(
array(
$overheated_message,
));
}
$view_all = id(new PHUIButtonView()) $view_all = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
@ -103,7 +118,11 @@ final class PhabricatorProjectProfileController
$feed = id(new PHUIObjectBoxView()) $feed = id(new PHUIObjectBoxView())
->setHeader($feed_header) ->setHeader($feed_header)
->addClass('project-view-feed') ->addClass('project-view-feed')
->appendChild($feed); ->appendChild(
array(
$overheated_view,
$feed,
));
require_celerity_resource('project-view-css'); require_celerity_resource('project-view-css');

View file

@ -4,7 +4,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
private $viewer; private $viewer;
private $boardPHIDs; private $boardPHIDs;
private $objectPHIDs; private $objectPHIDs = array();
private $boards; private $boards;
private $columnMap = array(); private $columnMap = array();
private $objectColumnMap = array(); private $objectColumnMap = array();

View file

@ -37,7 +37,8 @@ final class PhabricatorEpochEditField
} }
protected function newConduitParameterType() { protected function newConduitParameterType() {
return new ConduitEpochParameterType(); return id(new ConduitEpochParameterType())
->setAllowNull($this->getAllowNull());
} }
} }

View file

@ -121,6 +121,10 @@ When defining custom fields using a configuration option like
supported in text, int and remarkup fields (optional). supported in text, int and remarkup fields (optional).
- **copy**: If true, this field's value will be copied when an object is - **copy**: If true, this field's value will be copied when an object is
created using another object as a template. created using another object as a template.
- **limit**: For control types which use a tokenizer control to let the user
select a list of values, this limits how many values can be selected. For
example, a "users" field with a limit of "1" will behave like the "Owner"
field in Maniphest and only allow selection of a single user.
The `strings` value supports different strings per control type. They are: The `strings` value supports different strings per control type. They are:

View file

@ -0,0 +1,92 @@
@title Permanently Destroying Data
@group fieldmanual
How to permanently destroy data and manage leaked secrets.
Overview
========
Phabricator intentionally makes it difficult to permanently destroy data, but
provides a command-line tool for destroying objects if you're certain that
you want to destroy something.
**Disable vs Destroy**: Most kinds of objects can be disabled, deactivated,
closed, or archived. These operations place them in inactive states and
preserve their transaction history.
(NOTE) Disabling (rather than destroying) objects is strongly recommended
unless you have a very good reason to want to permanently destroy an object.
Destroying Data
===============
To permanently destroy an object, run this command from the command line:
```
phabricator/ $ ./bin/remove destroy <object>
```
The `<object>` may be an object monogram or PHID. For instance, you can use
`@alice` to destroy a particular user, or `T123` to destroy a particular task.
(IMPORTANT) This operation is permanent and can not be undone.
CLI Access Required
===================
In almost all cases, Phabricator requires operational access from the CLI to
permanently destroy data. One major reason for this requirement is that it
limits the reach of an attacker who compromises a privileged account.
The web UI is generally append-only and actions generally leave an audit
trail, usually in the transaction log. Thus, an attacker who compromises an
account but only gains access to the web UI usually can not do much permanent
damage and usually can not hide their actions or cover their tracks.
Another reason that destroying data is hard is simply that it's permanent and
can not be undone, so there's no way to recover from mistakes.
Leaked Secrets
==============
Sometimes you may want to destroy an object because it has leaked a secret,
like an API key or another credential. For example, an engineer might
accidentally send a change for review which includes a sensitive private key.
No Phabricator command can rewind time, and once data is written to Phabricator
the cat is often out of the bag: it has often been transmitted to external
systems which Phabricator can not interact with via email, webhooks, API calls,
repository mirroring, CDN caching, and so on. You can try to clean up the mess,
but you're generally already too late.
The `bin/remove destroy` command will make a reasonable attempt to completely
destroy objects, but this is just an attempt. It can not unsend email or uncall
the API, and no command can rewind time and undo a leak.
**Revoking Credentials**: If Phabricator credentials were accidentally
disclosed, you can revoke them so they no longer function. See
@{article:Revoking Credentials} for more information.
Preventing Leaks
================
Because time can not be rewound, it is best to prevent sensitive data from
leaking in the first place. Phabricator supports some technical measures that
can make it more difficult to accidentally disclose secrets:
**Differential Diff Herald Rules**: You can write "Differential Diff" rules
in Herald that reject diffs before they are written to disk by using the
"Block diff with message" action.
These rules can reject diffs based on affected file names or file content.
This is a coarse tool, but rejecting diffs which contain strings like
`BEGIN RSA PRIVATE KEY` may make it more difficult to accidentally disclose
certain secrets.
**Commit Content Herald Rules**: For hosted repositories, you can write
"Commit Hook: Commit Content" rules in Herald which reject pushes that contain
commit which match certain rules (like file name or file content rules).

View file

@ -169,8 +169,8 @@ start working normally.
Basics: Delete Repository Basics: Delete Repository
========================= =========================
Repositories can not be deleted from the web UI, so this option is always Repositories can not be deleted from the web UI, so this option only gives you
disabled. Clicking it gives you information about how to delete a repository. information about how to delete a repository.
Repositories can only be deleted from the command line, with `bin/remove`: Repositories can only be deleted from the command line, with `bin/remove`:
@ -178,9 +178,8 @@ Repositories can only be deleted from the command line, with `bin/remove`:
$ ./bin/remove destroy <repository> $ ./bin/remove destroy <repository>
``` ```
WARNING: This command will issue you a dire warning about the severity of the This command will permanently destroy the repository. For more information
action you are taking. Heed this warning. You are **strongly discouraged** from about destroying things, see @{article:Permanently Destroying Data}.
destroying repositories. Instead, deactivate them.
Policies Policies

View file

@ -226,20 +226,14 @@ final class PhabricatorStandardCustomFieldDate
} }
} }
public function shouldAppearInConduitTransactions() {
// TODO: Dates are complicated and we don't yet support handling them from
// Conduit.
return false;
}
protected function newConduitSearchParameterType() { protected function newConduitSearchParameterType() {
// TODO: Build a new "pair<epoch|null, epoch|null>" type or similar. // TODO: Build a new "pair<epoch|null, epoch|null>" type or similar.
return null; return null;
} }
protected function newConduitEditParameterType() { protected function newConduitEditParameterType() {
return new ConduitEpochParameterType(); return id(new ConduitEpochParameterType())
->setAllowNull(!$this->getRequired());
} }
protected function newExportFieldType() { protected function newExportFieldType() {

View file

@ -161,15 +161,36 @@ final class AphrontDialogView
} }
public function appendParagraph($paragraph) { public function appendParagraph($paragraph) {
return $this->appendChild( return $this->appendParagraphTag($paragraph);
phutil_tag(
'p',
array(
'class' => 'aphront-dialog-view-paragraph',
),
$paragraph));
} }
public function appendCommand($command) {
$command_tag = phutil_tag('tt', array(), $command);
return $this->appendParagraphTag(
$command_tag,
'aphront-dialog-view-command');
}
private function appendParagraphTag($content, $classes = null) {
if ($classes) {
$classes = (array)$classes;
} else {
$classes = array();
}
array_unshift($classes, 'aphront-dialog-view-paragraph');
$paragraph_tag = phutil_tag(
'p',
array(
'class' => implode(' ', $classes),
),
$content);
return $this->appendChild($paragraph_tag);
}
public function appendList(array $items) { public function appendList(array $items) {
$listitems = array(); $listitems = array();
foreach ($items as $item) { foreach ($items as $item) {

View file

@ -158,6 +158,11 @@
margin-top: 16px; margin-top: 16px;
} }
.aphront-dialog-view-command {
padding: 8px 16px;
background: {$greybackground};
}
.device-desktop .aphront-dialog-flush .phui-oi-list-view { .device-desktop .aphront-dialog-flush .phui-oi-list-view {
margin: 0; margin: 0;
padding: 0; padding: 0;

View file

@ -132,7 +132,7 @@ JX.behavior('phabricator-object-selector', function(config) {
var select_object_link = JX.$N( var select_object_link = JX.$N(
'a', 'a',
{href: h.uri, sigil: 'object-attacher'}, {href: '#', sigil: 'object-attacher'},
h.name); h.name);
var select_object_button = JX.$N( var select_object_button = JX.$N(