1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-10 14:51:06 +01:00

(stable) Promote 2018 Week 35

This commit is contained in:
epriestley 2018-09-04 10:15:49 -07:00
commit b0130f6d4e
93 changed files with 1544 additions and 681 deletions

View file

@ -9,8 +9,8 @@ return array(
'names' => array( 'names' => array(
'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65', 'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => 'fc4839c8', 'core.pkg.css' => 'badf3f16',
'core.pkg.js' => '2058ec09', 'core.pkg.js' => 'b5a949ca',
'differential.pkg.css' => '06dc617c', 'differential.pkg.css' => '06dc617c',
'differential.pkg.js' => 'c1cfa143', 'differential.pkg.js' => 'c1cfa143',
'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.css' => 'a2d17c7d',
@ -75,7 +75,7 @@ return array(
'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948',
'rsrc/css/application/flag/flag.css' => 'bba8f811', 'rsrc/css/application/flag/flag.css' => 'bba8f811',
'rsrc/css/application/harbormaster/harbormaster.css' => '730a4a3c', 'rsrc/css/application/harbormaster/harbormaster.css' => '7446ce72',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => 'cd8d0134', 'rsrc/css/application/herald/herald.css' => 'cd8d0134',
'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/report.css' => '9b9580b7',
@ -146,15 +146,15 @@ return array(
'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad',
'rsrc/css/phui/phui-crumbs-view.css' => '10728aaa', 'rsrc/css/phui/phui-crumbs-view.css' => '10728aaa',
'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026', 'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026',
'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', 'rsrc/css/phui/phui-document-pro.css' => '0e41dd91',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
'rsrc/css/phui/phui-document.css' => '878c2f52', 'rsrc/css/phui/phui-document.css' => 'c4ac41f9',
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01',
'rsrc/css/phui/phui-form-view.css' => 'f808e5be', 'rsrc/css/phui/phui-form-view.css' => 'f808e5be',
'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-form.css' => '7aaa04e3',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => 'edeb9252', 'rsrc/css/phui/phui-header-view.css' => '1ba8b707',
'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf',
'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee',
'rsrc/css/phui/phui-icon.css' => 'cf24ceec', 'rsrc/css/phui/phui-icon.css' => 'cf24ceec',
@ -473,7 +473,7 @@ return array(
'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '94b7c320', 'rsrc/js/core/behavior-phabricator-nav.js' => '9d32bc88',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee',
'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207',
'rsrc/js/core/behavior-redirect.js' => '0213259f', 'rsrc/js/core/behavior-redirect.js' => '0213259f',
@ -554,7 +554,7 @@ return array(
'font-fontawesome' => 'e838e088', 'font-fontawesome' => 'e838e088',
'font-lato' => 'c7ccd872', 'font-lato' => 'c7ccd872',
'global-drag-and-drop-css' => 'b556a948', 'global-drag-and-drop-css' => 'b556a948',
'harbormaster-css' => '730a4a3c', 'harbormaster-css' => '7446ce72',
'herald-css' => 'cd8d0134', 'herald-css' => 'cd8d0134',
'herald-rule-editor' => 'dca75c0e', 'herald-rule-editor' => 'dca75c0e',
'herald-test-css' => 'a52e323e', 'herald-test-css' => 'a52e323e',
@ -631,7 +631,7 @@ return array(
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0',
'javelin-behavior-phabricator-line-linker' => '66a62306', 'javelin-behavior-phabricator-line-linker' => '66a62306',
'javelin-behavior-phabricator-nav' => '94b7c320', 'javelin-behavior-phabricator-nav' => '9d32bc88',
'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '77c1f0b0', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0',
'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-oncopy' => '2926fff2',
@ -813,15 +813,15 @@ return array(
'phui-crumbs-view-css' => '10728aaa', 'phui-crumbs-view-css' => '10728aaa',
'phui-curtain-view-css' => '2bdaf026', 'phui-curtain-view-css' => '2bdaf026',
'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '878c2f52', 'phui-document-view-css' => 'c4ac41f9',
'phui-document-view-pro-css' => '8af7ea27', 'phui-document-view-pro-css' => '0e41dd91',
'phui-feed-story-css' => '44a9c8e9', 'phui-feed-story-css' => '44a9c8e9',
'phui-font-icon-base-css' => '870a7360', 'phui-font-icon-base-css' => '870a7360',
'phui-fontkit-css' => '1320ed01', 'phui-fontkit-css' => '1320ed01',
'phui-form-css' => '7aaa04e3', 'phui-form-css' => '7aaa04e3',
'phui-form-view-css' => 'f808e5be', 'phui-form-view-css' => 'f808e5be',
'phui-head-thing-view-css' => 'fd311e5f', 'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => 'edeb9252', 'phui-header-view-css' => '1ba8b707',
'phui-hovercard' => '1bd28176', 'phui-hovercard' => '1bd28176',
'phui-hovercard-view-css' => 'f0592bcf', 'phui-hovercard-view-css' => 'f0592bcf',
'phui-icon-set-selector-css' => '87db8fee', 'phui-icon-set-selector-css' => '87db8fee',
@ -1628,16 +1628,6 @@ return array(
'javelin-resource', 'javelin-resource',
'javelin-routable', 'javelin-routable',
), ),
'94b7c320' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-dom',
'javelin-magical-init',
'javelin-vector',
'javelin-request',
'javelin-util',
),
'960f6a39' => array( '960f6a39' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',
@ -1658,6 +1648,16 @@ return array(
'javelin-workflow', 'javelin-workflow',
'javelin-stratcom', 'javelin-stratcom',
), ),
'9d32bc88' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-dom',
'javelin-magical-init',
'javelin-vector',
'javelin-request',
'javelin-util',
),
'9d9685d6' => array( '9d9685d6' => array(
'phui-oi-list-view-css', 'phui-oi-list-view-css',
), ),

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
ADD acquiredEpoch INT UNSIGNED;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
ADD activatedEpoch INT UNSIGNED;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
ADD contentPHID VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_content
ADD documentPHID VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
ADD editedEpoch INT UNSIGNED NOT NULL;

View file

@ -0,0 +1,57 @@
<?php
// Update the PhrictionDocument and PhrictionContent tables to refer to one
// another by PHID instead of by ID.
$document_table = new PhrictionDocument();
$content_table = new PhrictionContent();
$conn = $document_table->establishConnection('w');
$document_iterator = new LiskRawMigrationIterator(
$conn,
$document_table->getTableName());
foreach ($document_iterator as $row) {
$content_id = $row['contentID'];
$content_row = queryfx_one(
$conn,
'SELECT phid, dateCreated FROM %T WHERE id = %d',
$content_table->getTableName(),
$content_id);
if (!$content_row) {
continue;
}
queryfx(
$conn,
'UPDATE %T SET contentPHID = %s, editedEpoch = %d WHERE id = %d',
$document_table->getTableName(),
$content_row['phid'],
$content_row['dateCreated'],
$row['id']);
}
$content_iterator = new LiskRawMigrationIterator(
$conn,
$content_table->getTableName());
foreach ($content_iterator as $row) {
$document_id = $row['documentID'];
$document_row = queryfx_one(
$conn,
'SELECT phid FROM %T WHERE id = %d',
$document_table->getTableName(),
$document_id);
if (!$document_row) {
continue;
}
queryfx(
$conn,
'UPDATE %T SET documentPHID = %s WHERE id = %d',
$content_table->getTableName(),
$document_row['phid'],
$row['id']);
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
DROP contentID;

View file

@ -0,0 +1,20 @@
<?php
// See T13193. We're about to drop the "documentID" column, which is part of
// a UNIQUE KEY. In MariaDB, we must first drop the "documentID" key or we get
// into deep trouble.
// There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch
// instead of an SQL patch.
$table = new PhrictionContent();
$conn = $table->establishConnection('w');
try {
queryfx(
$conn,
'ALTER TABLE %T DROP KEY documentID',
$table->getTableName());
} catch (AphrontQueryException $ex) {
// Ignore.
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_content
DROP documentID;

View file

@ -0,0 +1 @@
DELETE FROM {$NAMESPACE}_phriction.phriction_content WHERE documentPHID = '';

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_content
ADD UNIQUE KEY `key_version` (documentPHID, version);

View file

@ -0,0 +1,26 @@
<?php
$document_table = new PhrictionDocument();
$document_conn = $document_table->establishConnection('w');
$document_name = $document_table->getTableName();
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator($document_conn, $document_name);
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %T
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table->getTableName(),
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
DROP mailKey;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
ADD maxVersion INT UNSIGNED NOT NULL;

View file

@ -0,0 +1,30 @@
<?php
// Populate the "maxVersion" column by copying the maximum "version" from the
// content table.
$document_table = new PhrictionDocument();
$content_table = new PhrictionContent();
$conn = $document_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$conn,
$document_table->getTableName());
foreach ($iterator as $row) {
$content = queryfx_one(
$conn,
'SELECT MAX(version) max FROM %T WHERE documentPHID = %s',
$content_table->getTableName(),
$row['phid']);
if (!$content) {
continue;
}
queryfx(
$conn,
'UPDATE %T SET maxVersion = %d WHERE id = %d',
$document_table->getTableName(),
$content['max'],
$row['id']);
}

View file

@ -644,6 +644,7 @@ phutil_register_library_map(array(
'DifferentialRevisionStatusTransaction' => 'applications/differential/xaction/DifferentialRevisionStatusTransaction.php', 'DifferentialRevisionStatusTransaction' => 'applications/differential/xaction/DifferentialRevisionStatusTransaction.php',
'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php', 'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php',
'DifferentialRevisionSummaryTransaction' => 'applications/differential/xaction/DifferentialRevisionSummaryTransaction.php', 'DifferentialRevisionSummaryTransaction' => 'applications/differential/xaction/DifferentialRevisionSummaryTransaction.php',
'DifferentialRevisionTestPlanHeraldField' => 'applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php',
'DifferentialRevisionTestPlanTransaction' => 'applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php', 'DifferentialRevisionTestPlanTransaction' => 'applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php',
'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php', 'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php',
'DifferentialRevisionTitleTransaction' => 'applications/differential/xaction/DifferentialRevisionTitleTransaction.php', 'DifferentialRevisionTitleTransaction' => 'applications/differential/xaction/DifferentialRevisionTitleTransaction.php',
@ -1912,7 +1913,7 @@ phutil_register_library_map(array(
'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php', 'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php',
'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php',
'PHUIDocumentSummaryView' => 'view/phui/PHUIDocumentSummaryView.php', 'PHUIDocumentSummaryView' => 'view/phui/PHUIDocumentSummaryView.php',
'PHUIDocumentViewPro' => 'view/phui/PHUIDocumentViewPro.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php',
'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php',
'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php',
'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php',
@ -2042,6 +2043,7 @@ phutil_register_library_map(array(
'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php', 'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php', 'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php', 'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
'PeopleDisableUsersCapability' => 'applications/people/capability/PeopleDisableUsersCapability.php',
'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php', 'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php',
'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php', 'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php',
'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
@ -5028,6 +5030,7 @@ phutil_register_library_map(array(
'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php',
'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php',
'PhrictionDocumentPolicyCodex' => 'applications/phriction/codex/PhrictionDocumentPolicyCodex.php', 'PhrictionDocumentPolicyCodex' => 'applications/phriction/codex/PhrictionDocumentPolicyCodex.php',
'PhrictionDocumentPublishTransaction' => 'applications/phriction/xaction/PhrictionDocumentPublishTransaction.php',
'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php',
'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php', 'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php',
'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php', 'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php',
@ -5035,6 +5038,7 @@ phutil_register_library_map(array(
'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php',
'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php',
'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php', 'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php',
'PhrictionDocumentVersionTransaction' => 'applications/phriction/xaction/PhrictionDocumentVersionTransaction.php',
'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php', 'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php',
'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php',
'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php', 'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php',
@ -5044,6 +5048,7 @@ phutil_register_library_map(array(
'PhrictionMarkupPreviewController' => 'applications/phriction/controller/PhrictionMarkupPreviewController.php', 'PhrictionMarkupPreviewController' => 'applications/phriction/controller/PhrictionMarkupPreviewController.php',
'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php', 'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php',
'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php', 'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php',
'PhrictionPublishController' => 'applications/phriction/controller/PhrictionPublishController.php',
'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php', 'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php',
'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php',
'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php',
@ -6000,6 +6005,7 @@ phutil_register_library_map(array(
'DifferentialRevisionStatusTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionStatusTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTestPlanHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionTitleTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionTitleTransaction' => 'DifferentialRevisionTransactionType',
@ -7452,7 +7458,7 @@ phutil_register_library_map(array(
'PHUIDiffTableOfContentsListView' => 'AphrontView', 'PHUIDiffTableOfContentsListView' => 'AphrontView',
'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
'PHUIDocumentSummaryView' => 'AphrontTagView', 'PHUIDocumentSummaryView' => 'AphrontTagView',
'PHUIDocumentViewPro' => 'AphrontTagView', 'PHUIDocumentView' => 'AphrontTagView',
'PHUIFeedStoryExample' => 'PhabricatorUIExample', 'PHUIFeedStoryExample' => 'PhabricatorUIExample',
'PHUIFeedStoryView' => 'AphrontView', 'PHUIFeedStoryView' => 'AphrontView',
'PHUIFormDividerControl' => 'AphrontFormControl', 'PHUIFormDividerControl' => 'AphrontFormControl',
@ -7592,6 +7598,7 @@ phutil_register_library_map(array(
'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability', 'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability', 'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleDisableUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
@ -8288,10 +8295,7 @@ phutil_register_library_map(array(
'PhabricatorConfigManualActivity' => 'PhabricatorConfigEntryDAO', 'PhabricatorConfigManualActivity' => 'PhabricatorConfigEntryDAO',
'PhabricatorConfigModule' => 'Phobject', 'PhabricatorConfigModule' => 'Phobject',
'PhabricatorConfigModuleController' => 'PhabricatorConfigController', 'PhabricatorConfigModuleController' => 'PhabricatorConfigController',
'PhabricatorConfigOption' => array( 'PhabricatorConfigOption' => 'Phobject',
'Phobject',
'PhabricatorMarkupInterface',
),
'PhabricatorConfigOptionType' => 'Phobject', 'PhabricatorConfigOptionType' => 'Phobject',
'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule', 'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
@ -11130,27 +11134,29 @@ phutil_register_library_map(array(
), ),
'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentController' => 'PhrictionController',
'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource',
'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine',
'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine',
'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter',
'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldField' => 'HeraldField',
'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup',
'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType',
'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentPolicyCodex' => 'PhabricatorPolicyCodex', 'PhrictionDocumentPolicyCodex' => 'PhabricatorPolicyCodex',
'PhrictionDocumentPublishTransaction' => 'PhrictionDocumentTransactionType',
'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhrictionDocumentStatus' => 'PhabricatorObjectStatus', 'PhrictionDocumentStatus' => 'PhabricatorObjectStatus',
'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType',
'PhrictionDocumentVersionTransaction' => 'PhrictionDocumentTransactionType',
'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionEditController' => 'PhrictionController', 'PhrictionEditController' => 'PhrictionController',
'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod',
@ -11160,6 +11166,7 @@ phutil_register_library_map(array(
'PhrictionMarkupPreviewController' => 'PhabricatorController', 'PhrictionMarkupPreviewController' => 'PhabricatorController',
'PhrictionMoveController' => 'PhrictionController', 'PhrictionMoveController' => 'PhrictionController',
'PhrictionNewController' => 'PhrictionController', 'PhrictionNewController' => 'PhrictionController',
'PhrictionPublishController' => 'PhrictionController',
'PhrictionRemarkupRule' => 'PhutilRemarkupRule', 'PhrictionRemarkupRule' => 'PhutilRemarkupRule',
'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec',

View file

@ -153,30 +153,16 @@ final class PhabricatorConfigEditController
$e_value); $e_value);
} }
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($viewer);
$engine->addObject($option, 'description');
$engine->process();
$description = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($option, 'description'));
$form $form
->setUser($viewer) ->setUser($viewer)
->addHiddenInput('issue', $request->getStr('issue')); ->addHiddenInput('issue', $request->getStr('issue'));
$description = $option->getDescription(); $description = $option->newDescriptionRemarkupView($viewer);
if (strlen($description)) { if ($description) {
$description_view = new PHUIRemarkupView($viewer, $description); $form->appendChild(
id(new AphrontFormMarkupControl())
$form ->setLabel(pht('Description'))
->appendChild( ->setValue($description));
id(new AphrontFormMarkupControl())
->setLabel(pht('Description'))
->setValue($description_view));
} }
if ($group) { if ($group) {

View file

@ -60,17 +60,10 @@ final class PhabricatorConfigGroupController
$db_values = mpull($db_values, null, 'getConfigKey'); $db_values = mpull($db_values, null, 'getConfigKey');
} }
$engine = id(new PhabricatorMarkupEngine())
->setViewer($this->getRequest()->getUser());
foreach ($options as $option) {
$engine->addObject($option, 'summary');
}
$engine->process();
$list = new PHUIObjectItemListView(); $list = new PHUIObjectItemListView();
$list->setBig(true); $list->setBig(true);
foreach ($options as $option) { foreach ($options as $option) {
$summary = $engine->getOutput($option, 'summary'); $summary = $option->getSummary();
$item = id(new PHUIObjectItemView()) $item = id(new PHUIObjectItemView())
->setHeader($option->getKey()) ->setHeader($option->getKey())

View file

@ -33,27 +33,26 @@ final class PhabricatorConfigIssueListController
PhabricatorSetupCheck::GROUP_OTHER, PhabricatorSetupCheck::GROUP_OTHER,
'fa-question-circle'); 'fa-question-circle');
$no_issues = null; $title = pht('Setup Issues');
if (empty($issues)) { $header = $this->buildHeaderView($title);
$no_issues = id(new PHUIInfoView())
if (!$issues) {
$issue_list = id(new PHUIInfoView())
->setTitle(pht('No Issues')) ->setTitle(pht('No Issues'))
->appendChild( ->appendChild(
pht('Your install has no current setup issues to resolve.')) pht('Your install has no current setup issues to resolve.'))
->setSeverity(PHUIInfoView::SEVERITY_NOTICE); ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
} else {
$issue_list = array(
$important,
$php,
$mysql,
$other,
);
$issue_list = $this->buildConfigBoxView(pht('Issues'), $issue_list);
} }
$title = pht('Setup Issues');
$header = $this->buildHeaderView($title);
$issue_list = array(
$important,
$php,
$mysql,
$other,
);
$issue_list = $this->buildConfigBoxView(pht('Issues'), $issue_list);
$crumbs = $this->buildApplicationCrumbs() $crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($title) ->addTextCrumb($title)
->setBorder(true); ->setBorder(true);
@ -62,10 +61,7 @@ final class PhabricatorConfigIssueListController
->setHeader($header) ->setHeader($header)
->setNavigation($nav) ->setNavigation($nav)
->setFixed(true) ->setFixed(true)
->setMainColumn(array( ->setMainColumn($issue_list);
$no_issues,
$issue_list,
));
return $this->newPage() return $this->newPage()
->setTitle($title) ->setTitle($title)

View file

@ -1,8 +1,7 @@
<?php <?php
final class PhabricatorConfigOption final class PhabricatorConfigOption
extends Phobject extends Phobject {
implements PhabricatorMarkupInterface {
private $key; private $key;
private $default; private $default;
@ -204,43 +203,19 @@ final class PhabricatorConfigOption
return $this; return $this;
} }
/* -( PhabricatorMarkupInterface )----------------------------------------- */ public function newDescriptionRemarkupView(PhabricatorUser $viewer) {
$description = $this->getDescription();
public function getMarkupFieldKey($field) { if (!strlen($description)) {
return $this->getKey().':'.$field; return null;
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newMarkupEngine(array());
}
public function getMarkupText($field) {
switch ($field) {
case 'description':
$text = $this->getDescription();
break;
case 'summary':
$text = $this->getSummary();
break;
} }
// TODO: We should probably implement this as a real Markup rule, but // TODO: Some day, we should probably implement this as a real rule.
// markup rules are a bit of a mess right now and it doesn't hurt us to $description = preg_replace(
// fake this.
$text = preg_replace(
'/{{([^}]+)}}/', '/{{([^}]+)}}/',
'[[/config/edit/\\1/ | \\1]]', '[[/config/edit/\\1/ | \\1]]',
$text); $description);
return $text; return new PHUIRemarkupView($viewer, $description);
}
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return false;
} }
} }

View file

@ -146,49 +146,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
return $xactions; return $xactions;
} }
protected function requireCapabilities(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
parent::requireCapabilities($object, $xaction);
switch ($xaction->getTransactionType()) {
case ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE:
$old_map = array_fuse($xaction->getOldValue());
$new_map = array_fuse($xaction->getNewValue());
$add = array_keys(array_diff_key($new_map, $old_map));
$rem = array_keys(array_diff_key($old_map, $new_map));
$actor_phid = $this->getActingAsPHID();
$is_join = (($add === array($actor_phid)) && !$rem);
$is_leave = (($rem === array($actor_phid)) && !$add);
if ($is_join) {
// Anyone can join a thread they can see.
} else if ($is_leave) {
// Anyone can leave a thread.
} else {
// You need CAN_EDIT to add or remove participants. For additional
// discussion, see D17696 and T4411.
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
}
break;
case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE:
case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE:
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
}
}
protected function shouldSendMail( protected function shouldSendMail(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {

View file

@ -114,4 +114,34 @@ final class ConpherenceThreadParticipantsTransaction
return $errors; return $errors;
} }
public function getRequiredCapabilities(
$object,
PhabricatorApplicationTransaction $xaction) {
$old_map = array_fuse($xaction->getOldValue());
$new_map = array_fuse($xaction->getNewValue());
$add = array_keys(array_diff_key($new_map, $old_map));
$rem = array_keys(array_diff_key($old_map, $new_map));
$actor_phid = $this->getActingAsPHID();
$is_join = (($add === array($actor_phid)) && !$rem);
$is_leave = (($rem === array($actor_phid)) && !$add);
if ($is_join) {
// Anyone can join a thread they can see.
return null;
}
if ($is_leave) {
// Anyone can leave a thread.
return null;
}
// You need CAN_EDIT to add or remove participants. For additional
// discussion, see D17696 and T4411.
return PhabricatorPolicyCapability::CAN_EDIT;
}
} }

View file

@ -1315,7 +1315,7 @@ final class DifferentialRevisionViewController
} }
return id(new HarbormasterUnitSummaryView()) return id(new HarbormasterUnitSummaryView())
->setUser($viewer) ->setViewer($viewer)
->setExcuse($excuse) ->setExcuse($excuse)
->setBuildable($diff->getBuildable()) ->setBuildable($diff->getBuildable())
->setUnitMessages($diff->getUnitMessages()) ->setUnitMessages($diff->getUnitMessages())

View file

@ -54,4 +54,28 @@ final class DifferentialBuildableEngine
$this->applyTransactions(array($xaction)); $this->applyTransactions(array($xaction));
} }
public function getAuthorIdentity() {
$object = $this->getObject();
if ($object instanceof DifferentialRevision) {
$object = $object->loadActiveDiff();
}
$authorship = $object->getDiffAuthorshipDict();
if (!isset($authorship['authorName'])) {
return null;
}
$name = $authorship['authorName'];
$address = idx($authorship, 'authorEmail');
$full = id(new PhutilEmailAddress())
->setDisplayName($name)
->setAddress($address);
return id(new PhabricatorRepositoryIdentity())
->setIdentityName((string)$full)
->makeEphemeral();
}
} }

View file

@ -10,10 +10,7 @@ final class DifferentialRevisionSummaryHeraldField
} }
public function getHeraldFieldValue($object) { public function getHeraldFieldValue($object) {
// NOTE: For historical reasons, this field includes the test plan. We return $object->getSummary();
// could maybe try to fix this some day, but it probably aligns reasonably
// well with user expectation without harming anything.
return $object->getSummary()."\n\n".$object->getTestPlan();
} }
protected function getHeraldFieldStandardType() { protected function getHeraldFieldStandardType() {

View file

@ -0,0 +1,20 @@
<?php
final class DifferentialRevisionTestPlanHeraldField
extends DifferentialRevisionHeraldField {
const FIELDCONST = 'differential.revision.test-plan';
public function getHeraldFieldName() {
return pht('Revision test plan');
}
public function getHeraldFieldValue($object) {
return $object->getTestPlan();
}
protected function getHeraldFieldStandardType() {
return self::STANDARD_TEXT;
}
}

View file

@ -34,4 +34,10 @@ final class DiffusionBuildableEngine
$this->applyTransactions(array($xaction)); $this->applyTransactions(array($xaction));
} }
public function getAuthorIdentity() {
return $this->getObject()
->loadIdentities($this->getViewer())
->getAuthorIdentity();
}
} }

View file

@ -94,7 +94,7 @@ final class DiffusionReadmeView extends DiffusionView {
} }
$readme_content = phutil_tag_div($class, $readme_content); $readme_content = phutil_tag_div($class, $readme_content);
$document = id(new PHUIDocumentViewPro()) $document = id(new PHUIDocumentView())
->setFluid(true) ->setFluid(true)
->appendChild($readme_content); ->appendChild($readme_content);

View file

@ -72,7 +72,7 @@ final class DivinerAtomController extends DivinerController {
$prop_list = new PHUIPropertyGroupView(); $prop_list = new PHUIPropertyGroupView();
$prop_list->addPropertyList($properties); $prop_list->addPropertyList($properties);
$document = id(new PHUIDocumentViewPro()) $document = id(new PHUIDocumentView())
->setBook($book->getTitle(), $group_name) ->setBook($book->getTitle(), $group_name)
->setHeader($header) ->setHeader($header)
->addClass('diviner-view'); ->addClass('diviner-view');

View file

@ -45,7 +45,7 @@ final class DivinerBookController extends DivinerController {
->setName($book->getRepository()->getMonogram())); ->setName($book->getRepository()->getMonogram()));
} }
$document = new PHUIDocumentViewPro(); $document = new PHUIDocumentView();
$document->setHeader($header); $document->setHeader($header);
$document->addClass('diviner-view'); $document->addClass('diviner-view');

View file

@ -27,7 +27,7 @@ final class DivinerMainController extends DivinerController {
->setHeader(pht('Documentation Books')) ->setHeader(pht('Documentation Books'))
->addActionLink($query_button); ->addActionLink($query_button);
$document = new PHUIDocumentViewPro(); $document = new PHUIDocumentView();
$document->setHeader($header); $document->setHeader($header);
$document->addClass('diviner-view'); $document->addClass('diviner-view');

View file

@ -163,6 +163,30 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
} }
$view->addProperty(pht('Expires'), $until_display); $view->addProperty(pht('Expires'), $until_display);
$acquired_epoch = $lease->getAcquiredEpoch();
$activated_epoch = $lease->getActivatedEpoch();
if ($acquired_epoch) {
$acquired_display = phabricator_datetime($acquired_epoch, $viewer);
} else {
if ($activated_epoch) {
$acquired_display = phutil_tag(
'em',
array(),
pht('Activated on Acquisition'));
} else {
$acquired_display = phutil_tag('em', array(), pht('Not Acquired'));
}
}
$view->addProperty(pht('Acquired'), $acquired_display);
if ($activated_epoch) {
$activated_display = phabricator_datetime($activated_epoch, $viewer);
} else {
$activated_display = phutil_tag('em', array(), pht('Not Activated'));
}
$view->addProperty(pht('Activated'), $activated_display);
$attributes = $lease->getAttributes(); $attributes = $lease->getAttributes();
if ($attributes) { if ($attributes) {
$view->addSectionHeader( $view->addSectionHeader(

View file

@ -10,6 +10,8 @@ final class DrydockLease extends DrydockDAO
protected $authorizingPHID; protected $authorizingPHID;
protected $attributes = array(); protected $attributes = array();
protected $status = DrydockLeaseStatus::STATUS_PENDING; protected $status = DrydockLeaseStatus::STATUS_PENDING;
protected $acquiredEpoch;
protected $activatedEpoch;
private $resource = self::ATTACHABLE; private $resource = self::ATTACHABLE;
private $unconsumedCommands = self::ATTACHABLE; private $unconsumedCommands = self::ATTACHABLE;
@ -62,6 +64,22 @@ final class DrydockLease extends DrydockDAO
$this->scheduleUpdate(); $this->scheduleUpdate();
} }
public function setStatus($status) {
if ($status == DrydockLeaseStatus::STATUS_ACQUIRED) {
if (!$this->getAcquiredEpoch()) {
$this->setAcquiredEpoch(PhabricatorTime::getNow());
}
}
if ($status == DrydockLeaseStatus::STATUS_ACTIVE) {
if (!$this->getActivatedEpoch()) {
$this->setActivatedEpoch(PhabricatorTime::getNow());
}
}
return parent::setStatus($status);
}
public function getLeaseName() { public function getLeaseName() {
return pht('Lease %d', $this->getID()); return pht('Lease %d', $this->getID());
} }
@ -78,6 +96,8 @@ final class DrydockLease extends DrydockDAO
'resourceType' => 'text128', 'resourceType' => 'text128',
'ownerPHID' => 'phid?', 'ownerPHID' => 'phid?',
'resourcePHID' => 'phid?', 'resourcePHID' => 'phid?',
'acquiredEpoch' => 'epoch?',
'activatedEpoch' => 'epoch?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_resource' => array( 'key_resource' => array(

View file

@ -169,7 +169,7 @@ final class PhabricatorGuideInstallModule extends PhabricatorGuideModule {
$intro = new PHUIRemarkupView($viewer, $intro); $intro = new PHUIRemarkupView($viewer, $intro);
$intro = id(new PHUIDocumentViewPro()) $intro = id(new PHUIDocumentView())
->appendChild($intro); ->appendChild($intro);
return array($intro, $guide_items); return array($intro, $guide_items);

View file

@ -206,7 +206,7 @@ final class PhabricatorGuideQuickStartModule extends PhabricatorGuideModule {
'these features at your own pace.'); 'these features at your own pace.');
$intro = new PHUIRemarkupView($viewer, $intro); $intro = new PHUIRemarkupView($viewer, $intro);
$intro = id(new PHUIDocumentViewPro()) $intro = id(new PHUIDocumentView())
->appendChild($intro); ->appendChild($intro);
return array($intro, $guide_items); return array($intro, $guide_items);

View file

@ -318,7 +318,7 @@ final class HarbormasterBuildableViewController
if ($lint_data) { if ($lint_data) {
$lint_table = id(new HarbormasterLintPropertyView()) $lint_table = id(new HarbormasterLintPropertyView())
->setUser($viewer) ->setViewer($viewer)
->setLimit(10) ->setLimit(10)
->setLintMessages($lint_data); ->setLintMessages($lint_data);
@ -343,6 +343,7 @@ final class HarbormasterBuildableViewController
if ($unit_data) { if ($unit_data) {
$unit = id(new HarbormasterUnitSummaryView()) $unit = id(new HarbormasterUnitSummaryView())
->setViewer($viewer)
->setBuildable($buildable) ->setBuildable($buildable)
->setUnitMessages($unit_data) ->setUnitMessages($unit_data)
->setShowViewAll(true) ->setShowViewAll(true)

View file

@ -39,6 +39,7 @@ final class HarbormasterUnitMessageListController
} }
$unit = id(new HarbormasterUnitSummaryView()) $unit = id(new HarbormasterUnitSummaryView())
->setViewer($viewer)
->setBuildable($buildable) ->setBuildable($buildable)
->setUnitMessages($unit_data); ->setUnitMessages($unit_data);

View file

@ -88,23 +88,7 @@ final class HarbormasterUnitMessageViewController
pht('Run At'), pht('Run At'),
phabricator_datetime($message->getDateCreated(), $viewer)); phabricator_datetime($message->getDateCreated(), $viewer));
$details = $message->getUnitMessageDetails(); $details = $message->newUnitMessageDetailsView($viewer);
if (strlen($details)) {
// TODO: Use the log view here, once it gets cleaned up.
// Shenanigans below.
$details = phutil_tag(
'div',
array(
'class' => 'PhabricatorMonospaced',
'style' =>
'white-space: pre-wrap; '.
'color: #666666; '.
'overflow-x: auto;',
),
$details);
} else {
$details = phutil_tag('em', array(), pht('No details provided.'));
}
$view->addSectionHeader( $view->addSectionHeader(
pht('Details'), pht('Details'),

View file

@ -101,5 +101,8 @@ abstract class HarbormasterBuildableEngine
$xactions); $xactions);
} }
public function getAuthorIdentity() {
return null;
}
} }

View file

@ -117,6 +117,20 @@ EOTEXT
), ),
); );
$engine = HarbormasterBuildableEngine::newForObject(
$object,
$viewer);
$author_identity = $engine->getAuthorIdentity();
if ($author_identity) {
$data_structure += array(
'author' => array(
'name' => $author_identity->getIdentityDisplayName(),
'email' => $author_identity->getIdentityEmailAddress(),
),
);
}
$json_data = phutil_json_encode($data_structure); $json_data = phutil_json_encode($data_structure);
$credential_phid = $this->getSetting('token'); $credential_phid = $this->getSetting('token');

View file

@ -13,6 +13,9 @@ final class HarbormasterBuildUnitMessage
private $buildTarget = self::ATTACHABLE; private $buildTarget = self::ATTACHABLE;
const FORMAT_TEXT = 'text';
const FORMAT_REMARKUP = 'remarkup';
public static function initializeNewUnitMessage( public static function initializeNewUnitMessage(
HarbormasterBuildTarget $build_target) { HarbormasterBuildTarget $build_target) {
return id(new HarbormasterBuildUnitMessage()) return id(new HarbormasterBuildUnitMessage())
@ -66,6 +69,13 @@ final class HarbormasterBuildUnitMessage
'description' => pht( 'description' => pht(
'Additional human-readable information about the failure.'), 'Additional human-readable information about the failure.'),
), ),
'format' => array(
'type' => 'optional string',
'description' => pht(
'Format for the text provided in "details". Valid values are '.
'"text" (default) or "remarkup". This controls how test details '.
'are rendered when shown to users.'),
),
); );
} }
@ -104,6 +114,11 @@ final class HarbormasterBuildUnitMessage
$obj->setProperty('details', $details); $obj->setProperty('details', $details);
} }
$format = idx($dict, 'format');
if ($format) {
$obj->setProperty('format', $format);
}
return $obj; return $obj;
} }
@ -149,6 +164,66 @@ final class HarbormasterBuildUnitMessage
return $this->getProperty('details', ''); return $this->getProperty('details', '');
} }
public function getUnitMessageDetailsFormat() {
return $this->getProperty('format', self::FORMAT_TEXT);
}
public function newUnitMessageDetailsView(
PhabricatorUser $viewer,
$summarize = false) {
$format = $this->getUnitMessageDetailsFormat();
$is_text = ($format !== self::FORMAT_REMARKUP);
$is_remarkup = ($format === self::FORMAT_REMARKUP);
$full_details = $this->getUnitMessageDetails();
if (!strlen($full_details)) {
if ($summarize) {
return null;
}
$details = phutil_tag('em', array(), pht('No details provided.'));
} else if ($summarize) {
if ($is_text) {
$details = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(2048)
->truncateString($full_details);
$details = phutil_split_lines($details);
$limit = 3;
if (count($details) > $limit) {
$details = array_slice($details, 0, $limit);
}
$details = implode('', $details);
} else {
$details = $full_details;
}
} else {
$details = $full_details;
}
require_celerity_resource('harbormaster-css');
$classes = array();
$classes[] = 'harbormaster-unit-details';
if ($is_remarkup) {
$details = new PHUIRemarkupView($viewer, $details);
} else {
$classes[] = 'harbormaster-unit-details-text';
$classes[] = 'PhabricatorMonospaced';
}
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$details);
}
public function getUnitMessageDisplayName() { public function getUnitMessageDisplayName() {
$name = $this->getName(); $name = $this->getName();

View file

@ -34,9 +34,8 @@ final class HarbormasterUnitPropertyView extends AphrontView {
return $this; return $this;
} }
public function render() { public function render() {
require_celerity_resource('harbormaster-css'); $viewer = $this->getViewer();
$messages = $this->unitMessages; $messages = $this->unitMessages;
$messages = msort($messages, 'getSortKey'); $messages = msort($messages, 'getSortKey');
@ -84,13 +83,10 @@ final class HarbormasterUnitPropertyView extends AphrontView {
$name); $name);
} }
$details = $message->getUnitMessageDetails(); $name = array(
if (strlen($details)) { $name,
$name = array( $message->newUnitMessageDetailsView($viewer, true),
$name, );
$this->renderUnitTestDetails($details),
);
}
$rows[] = array( $rows[] = array(
$result_icon, $result_icon,
@ -158,25 +154,4 @@ final class HarbormasterUnitPropertyView extends AphrontView {
return $table; return $table;
} }
private function renderUnitTestDetails($full_details) {
$details = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(2048)
->truncateString($full_details);
$details = phutil_split_lines($details);
$limit = 3;
if (count($details) > $limit) {
$details = array_slice($details, 0, $limit);
}
$details = implode('', $details);
return phutil_tag(
'div',
array(
'class' => 'PhabricatorMonospaced harbormaster-unit-details',
),
$details);
}
} }

View file

@ -77,6 +77,7 @@ final class HarbormasterUnitSummaryView extends AphrontView {
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
$table = id(new HarbormasterUnitPropertyView()) $table = id(new HarbormasterUnitPropertyView())
->setViewer($this->getViewer())
->setUnitMessages($messages); ->setUnitMessages($messages);
if ($this->showViewAll) { if ($this->showViewAll) {

View file

@ -272,7 +272,7 @@ final class LegalpadDocumentSignController extends LegalpadController {
$preamble_box->addPropertyList($preamble); $preamble_box->addPropertyList($preamble);
} }
$content = id(new PHUIDocumentViewPro()) $content = id(new PHUIDocumentView())
->addClass('legalpad') ->addClass('legalpad')
->setHeader($header) ->setHeader($header)
->appendChild( ->appendChild(

View file

@ -132,7 +132,7 @@ final class PhabricatorApplicationEmailCommandsController
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader($title); ->setHeader($title);
$document = id(new PHUIDocumentViewPro()) $document = id(new PHUIDocumentView())
->setHeader($header) ->setHeader($header)
->appendChild($info_view) ->appendChild($info_view)
->appendChild($content_box); ->appendChild($content_box);

View file

@ -97,6 +97,9 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
PeopleCreateUsersCapability::CAPABILITY => array( PeopleCreateUsersCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN, 'default' => PhabricatorPolicies::POLICY_ADMIN,
), ),
PeopleDisableUsersCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
PeopleBrowseUserDirectoryCapability::CAPABILITY => array(), PeopleBrowseUserDirectoryCapability::CAPABILITY => array(),
); );
} }

View file

@ -0,0 +1,16 @@
<?php
final class PeopleDisableUsersCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'people.disable.users';
public function getCapabilityName() {
return pht('Can Disable Users');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to disable or enable users.');
}
}

View file

@ -10,6 +10,14 @@ final class UserDisableConduitAPIMethod extends UserConduitAPIMethod {
return pht('Permanently disable specified users (admin only).'); return pht('Permanently disable specified users (admin only).');
} }
public function getMethodStatus() {
return self::METHOD_STATUS_DEPRECATED;
}
public function getMethodStatusDescription() {
return pht('Obsoleted by method "user.edit".');
}
protected function defineParamTypes() { protected function defineParamTypes() {
return array( return array(
'phids' => 'required list<phid>', 'phids' => 'required list<phid>',
@ -43,11 +51,23 @@ final class UserDisableConduitAPIMethod extends UserConduitAPIMethod {
throw new ConduitException('ERR-BAD-PHID'); throw new ConduitException('ERR-BAD-PHID');
} }
foreach ($users as $user) { foreach ($phids as $phid) {
id(new PhabricatorUserEditor()) $params = array(
->setActor($actor) 'transactions' => array(
->disableUser($user, true); array(
'type' => 'disabled',
'value' => true,
),
),
'objectIdentifier' => $phid,
);
id(new ConduitCall('user.edit', $params))
->setUser($actor)
->execute();
} }
return null;
} }
} }

View file

@ -10,6 +10,14 @@ final class UserEnableConduitAPIMethod extends UserConduitAPIMethod {
return pht('Re-enable specified users (admin only).'); return pht('Re-enable specified users (admin only).');
} }
public function getMethodStatus() {
return self::METHOD_STATUS_DEPRECATED;
}
public function getMethodStatusDescription() {
return pht('Obsoleted by method "user.edit".');
}
protected function defineParamTypes() { protected function defineParamTypes() {
return array( return array(
'phids' => 'required list<phid>', 'phids' => 'required list<phid>',
@ -43,11 +51,23 @@ final class UserEnableConduitAPIMethod extends UserConduitAPIMethod {
throw new ConduitException('ERR-BAD-PHID'); throw new ConduitException('ERR-BAD-PHID');
} }
foreach ($users as $user) { foreach ($phids as $phid) {
id(new PhabricatorUserEditor()) $params = array(
->setActor($actor) 'transactions' => array(
->disableUser($user, false); array(
'type' => 'disabled',
'value' => false,
),
),
'objectIdentifier' => $phid,
);
id(new ConduitCall('user.edit', $params))
->setUser($actor)
->execute();
} }
return null;
} }
} }

View file

@ -16,6 +16,13 @@ final class PhabricatorPeopleApproveController
$done_uri = $this->getApplicationURI('query/approval/'); $done_uri = $this->getApplicationURI('query/approval/');
if ($user->getIsApproved()) {
return $this->newDialog()
->setTitle(pht('Already Approved'))
->appendChild(pht('This user has already been approved.'))
->addCancelButton($done_uri);
}
if ($request->isFormPost()) { if ($request->isFormPost()) {
id(new PhabricatorUserEditor()) id(new PhabricatorUserEditor())
->setActor($viewer) ->setActor($viewer)

View file

@ -3,10 +3,14 @@
final class PhabricatorPeopleDisableController final class PhabricatorPeopleDisableController
extends PhabricatorPeopleController { extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
return false;
}
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$id = $request->getURIData('id'); $id = $request->getURIData('id');
$via = $request->getURIData('id'); $via = $request->getURIData('via');
$user = id(new PhabricatorPeopleQuery()) $user = id(new PhabricatorPeopleQuery())
->setViewer($viewer) ->setViewer($viewer)
@ -20,11 +24,36 @@ final class PhabricatorPeopleDisableController
// on profiles and also via the "X" action on the approval queue. We do // on profiles and also via the "X" action on the approval queue. We do
// things slightly differently depending on the context the actor is in. // things slightly differently depending on the context the actor is in.
// In particular, disabling via "Disapprove" requires you be an
// administrator (and bypasses the "Can Disable Users" permission).
// Disabling via "Disable" requires the permission only.
$is_disapprove = ($via == 'disapprove'); $is_disapprove = ($via == 'disapprove');
if ($is_disapprove) { if ($is_disapprove) {
$done_uri = $this->getApplicationURI('query/approval/'); $done_uri = $this->getApplicationURI('query/approval/');
if (!$viewer->getIsAdmin()) {
return $this->newDialog()
->setTitle(pht('No Permission'))
->appendParagraph(pht('Only administrators can disapprove users.'))
->addCancelButton($done_uri);
}
if ($user->getIsApproved()) {
return $this->newDialog()
->setTitle(pht('Already Approved'))
->appendParagraph(pht('This user has already been approved.'))
->addCancelButton($done_uri);
}
// On the "Disapprove" flow, bypass the "Can Disable Users" permission.
$actor = PhabricatorUser::getOmnipotentUser();
$should_disable = true; $should_disable = true;
} else { } else {
$this->requireApplicationCapability(
PeopleDisableUsersCapability::CAPABILITY);
$actor = $viewer;
$done_uri = $this->getApplicationURI("manage/{$id}/"); $done_uri = $this->getApplicationURI("manage/{$id}/");
$should_disable = !$user->getIsDisabled(); $should_disable = !$user->getIsDisabled();
} }
@ -39,9 +68,19 @@ final class PhabricatorPeopleDisableController
} }
if ($request->isFormPost()) { if ($request->isFormPost()) {
id(new PhabricatorUserEditor()) $xactions = array();
->setActor($viewer)
->disableUser($user, $should_disable); $xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE)
->setNewValue($should_disable);
id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($viewer->getPHID())
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($user, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri); return id(new AphrontRedirectResponse())->setURI($done_uri);
} }

View file

@ -75,11 +75,22 @@ final class PhabricatorPeopleProfileManageController
private function buildCurtain(PhabricatorUser $user) { private function buildCurtain(PhabricatorUser $user) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$is_self = ($user->getPHID() === $viewer->getPHID());
$can_edit = PhabricatorPolicyFilter::hasCapability( $can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer, $viewer,
$user, $user,
PhabricatorPolicyCapability::CAN_EDIT); PhabricatorPolicyCapability::CAN_EDIT);
$is_admin = $viewer->getIsAdmin();
$can_admin = ($is_admin && !$is_self);
$has_disable = $this->hasApplicationCapability(
PeopleDisableUsersCapability::CAPABILITY);
$can_disable = ($has_disable && !$is_self);
$can_welcome = ($is_admin && $user->canEstablishWebSessions());
$curtain = $this->newCurtainView($user); $curtain = $this->newCurtainView($user);
$curtain->addAction( $curtain->addAction(
@ -114,10 +125,6 @@ final class PhabricatorPeopleProfileManageController
$empower_name = pht('Make Administrator'); $empower_name = pht('Make Administrator');
} }
$is_admin = $viewer->getIsAdmin();
$is_self = ($user->getPHID() === $viewer->getPHID());
$can_admin = ($is_admin && !$is_self);
$curtain->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setIcon($empower_icon) ->setIcon($empower_icon)
@ -146,7 +153,7 @@ final class PhabricatorPeopleProfileManageController
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setIcon($disable_icon) ->setIcon($disable_icon)
->setName($disable_name) ->setName($disable_name)
->setDisabled(!$can_admin) ->setDisabled(!$can_disable)
->setWorkflow(true) ->setWorkflow(true)
->setHref($this->getApplicationURI('disable/'.$user->getID().'/'))); ->setHref($this->getApplicationURI('disable/'.$user->getID().'/')));
@ -158,8 +165,6 @@ final class PhabricatorPeopleProfileManageController
->setWorkflow(true) ->setWorkflow(true)
->setHref($this->getApplicationURI('delete/'.$user->getID().'/'))); ->setHref($this->getApplicationURI('delete/'.$user->getID().'/')));
$can_welcome = ($is_admin && $user->canEstablishWebSessions());
$curtain->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setIcon('fa-envelope') ->setIcon('fa-envelope')

View file

@ -293,45 +293,6 @@ final class PhabricatorUserEditor extends PhabricatorEditor {
return $this; return $this;
} }
/**
* @task role
*/
public function disableUser(PhabricatorUser $user, $disable) {
$actor = $this->requireActor();
if (!$user->getID()) {
throw new Exception(pht('User has not been created yet!'));
}
$user->openTransaction();
$user->beginWriteLocking();
$user->reload();
if ($user->getIsDisabled() == $disable) {
$user->endWriteLocking();
$user->killTransaction();
return $this;
}
$log = PhabricatorUserLog::initializeNewLog(
$actor,
$user->getPHID(),
PhabricatorUserLog::ACTION_DISABLE);
$log->setOldValue($user->getIsDisabled());
$log->setNewValue($disable);
$user->setIsDisabled((int)$disable);
$user->save();
$log->save();
$user->endWriteLocking();
$user->saveTransaction();
return $this;
}
/** /**
* @task role * @task role
*/ */

View file

@ -60,6 +60,10 @@ final class PhabricatorUserDisableTransaction
continue; continue;
} }
// You must have the "Can Disable Users" permission to disable a user.
$this->requireApplicationCapability(
PeopleDisableUsersCapability::CAPABILITY);
if ($this->getActingAsPHID() === $object->getPHID()) { if ($this->getActingAsPHID() === $object->getPHID()) {
$errors[] = $this->newInvalidError( $errors[] = $this->newInvalidError(
pht('You can not enable or disable your own account.')); pht('You can not enable or disable your own account.'));
@ -69,4 +73,14 @@ final class PhabricatorUserDisableTransaction
return $errors; return $errors;
} }
public function getRequiredCapabilities(
$object,
PhabricatorApplicationTransaction $xaction) {
// You do not need to be able to edit users to disable them. Instead, this
// requirement is replaced with a requirement that you have the "Can
// Disable Users" permission.
return null;
}
} }

View file

@ -84,7 +84,7 @@ final class PhameHomeController extends PhamePostController {
pht('Recent Posts'), pht('Recent Posts'),
$this->getApplicationURI('post/')); $this->getApplicationURI('post/'));
$page = id(new PHUIDocumentViewPro()) $page = id(new PHUIDocumentView())
->setHeader($header) ->setHeader($header)
->appendChild($post_list); ->appendChild($post_list);

View file

@ -85,7 +85,7 @@ final class PhameBlogViewController extends PhameLiveController {
} }
} }
$page = id(new PHUIDocumentViewPro()) $page = id(new PHUIDocumentView())
->setHeader($header) ->setHeader($header)
->appendChild($post_list); ->appendChild($post_list);

View file

@ -30,7 +30,7 @@ final class PhamePostViewController
$header->setActionList($actions); $header->setActionList($actions);
} }
$document = id(new PHUIDocumentViewPro()) $document = id(new PHUIDocumentView())
->setHeader($header); ->setHeader($header);
if ($moved) { if ($moved) {

View file

@ -56,6 +56,8 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication {
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'PhrictionEditController', 'edit/(?:(?P<id>[1-9]\d*)/)?' => 'PhrictionEditController',
'delete/(?P<id>[1-9]\d*)/' => 'PhrictionDeleteController', 'delete/(?P<id>[1-9]\d*)/' => 'PhrictionDeleteController',
'publish/(?P<documentID>[1-9]\d*)/(?P<contentID>[1-9]\d*)/'
=> 'PhrictionPublishController',
'new/' => 'PhrictionNewController', 'new/' => 'PhrictionNewController',
'move/(?P<id>[1-9]\d*)/' => 'PhrictionMoveController', 'move/(?P<id>[1-9]\d*)/' => 'PhrictionMoveController',

View file

@ -65,8 +65,8 @@ final class PhrictionDiffController extends PhrictionController {
$slug = $document->getSlug(); $slug = $document->getSlug();
$revert_l = $this->renderRevertButton($content_l, $current); $revert_l = $this->renderRevertButton($document, $content_l, $current);
$revert_r = $this->renderRevertButton($content_r, $current); $revert_r = $this->renderRevertButton($document, $content_r, $current);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumb_views = $this->renderBreadcrumbs($slug); $crumb_views = $this->renderBreadcrumbs($slug);
@ -179,10 +179,11 @@ final class PhrictionDiffController extends PhrictionController {
} }
private function renderRevertButton( private function renderRevertButton(
PhrictionDocument $document,
PhrictionContent $content, PhrictionContent $content,
PhrictionContent $current) { PhrictionContent $current) {
$document_id = $content->getDocumentID(); $document_id = $document->getID();
$version = $content->getVersion(); $version = $content->getVersion();
$hidden_statuses = array( $hidden_statuses = array(

View file

@ -20,8 +20,6 @@ final class PhrictionDocumentController
return id(new AphrontRedirectResponse())->setURI($uri); return id(new AphrontRedirectResponse())->setURI($uri);
} }
require_celerity_resource('phriction-document-css');
$version_note = null; $version_note = null;
$core_content = ''; $core_content = '';
$move_notice = ''; $move_notice = '';
@ -29,6 +27,8 @@ final class PhrictionDocumentController
$content = null; $content = null;
$toc = null; $toc = null;
$is_draft = false;
$document = id(new PhrictionDocumentQuery()) $document = id(new PhrictionDocumentQuery())
->setViewer($viewer) ->setViewer($viewer)
->withSlugs(array($slug)) ->withSlugs(array($slug))
@ -66,8 +66,9 @@ final class PhrictionDocumentController
->addAction($create_button); ->addAction($create_button);
} else { } else {
$version = $request->getInt('v'); $max_version = (int)$document->getMaxVersion();
$version = $request->getInt('v');
if ($version) { if ($version) {
$content = id(new PhrictionContentQuery()) $content = id(new PhrictionContentQuery())
->setViewer($viewer) ->setViewer($viewer)
@ -78,15 +79,111 @@ final class PhrictionDocumentController
return new Aphront404Response(); return new Aphront404Response();
} }
if ($content->getID() != $document->getContentID()) { // When the "v" parameter exists, the user is in history mode so we
$version_note = id(new PHUIInfoView()) // show this header even if they're looking at the current version
->setSeverity(PHUIInfoView::SEVERITY_NOTICE) // of the document. This keeps the next/previous links working.
->appendChild(
pht( $view_version = (int)$content->getVersion();
'You are viewing an older version of this document, as it '. $published_version = (int)$document->getContent()->getVersion();
'appeared on %s.',
phabricator_datetime($content->getDateCreated(), $viewer))); if ($view_version < $published_version) {
$version_note = pht(
'You are viewing an older version of this document, as it '.
'appeared on %s.',
phabricator_datetime($content->getDateCreated(), $viewer));
} else if ($view_version > $published_version) {
$is_draft = true;
$version_note = pht(
'You are viewing an unpublished draft of this document.');
} else {
$version_note = pht(
'You are viewing the current published version of this document.');
} }
$version_note = array(
phutil_tag(
'strong',
array(),
pht('Version %d of %d: ', $view_version, $max_version)),
' ',
$version_note,
);
$version_note = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild($version_note);
$document_uri = new PhutilURI($document->getURI());
if ($view_version > 1) {
$previous_uri = $document_uri->alter('v', ($view_version - 1));
} else {
$previous_uri = null;
}
if ($view_version !== $published_version) {
$current_uri = $document_uri->alter('v', $published_version);
} else {
$current_uri = null;
}
if ($view_version < $max_version) {
$next_uri = $document_uri->alter('v', ($view_version + 1));
} else {
$next_uri = null;
}
if ($view_version !== $max_version) {
$draft_uri = $document_uri->alter('v', $max_version);
} else {
$draft_uri = null;
}
$button_bar = id(new PHUIButtonBarView())
->addButton(
id(new PHUIButtonView())
->setTag('a')
->setColor('grey')
->setIcon('fa-backward')
->setDisabled(!$previous_uri)
->setHref($previous_uri)
->setText(pht('Previous')))
->addButton(
id(new PHUIButtonView())
->setTag('a')
->setColor('grey')
->setIcon('fa-file-o')
->setDisabled(!$current_uri)
->setHref($current_uri)
->setText(pht('Published')))
->addButton(
id(new PHUIButtonView())
->setTag('a')
->setColor('grey')
->setIcon('fa-forward', false)
->setDisabled(!$next_uri)
->setHref($next_uri)
->setText(pht('Next')))
->addButton(
id(new PHUIButtonView())
->setTag('a')
->setColor('grey')
->setIcon('fa-fast-forward', false)
->setDisabled(!$draft_uri)
->setHref($draft_uri)
->setText(pht('Draft')));
require_celerity_resource('phui-document-view-css');
$version_note = array(
$version_note,
phutil_tag(
'div',
array(
'class' => 'phui-document-version-navigation',
),
$button_bar),
);
} else { } else {
$content = $document->getContent(); $content = $document->getContent();
} }
@ -201,7 +298,10 @@ final class PhrictionDocumentController
$children = $this->renderDocumentChildren($slug); $children = $this->renderDocumentChildren($slug);
$actions = $this->buildActionView($viewer, $document); $curtain = null;
if ($document->getID()) {
$curtain = $this->buildCurtain($document, $content);
}
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true); $crumbs->setBorder(true);
@ -213,10 +313,17 @@ final class PhrictionDocumentController
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setUser($viewer) ->setUser($viewer)
->setPolicyObject($document) ->setPolicyObject($document)
->setHeader($page_title) ->setHeader($page_title);
->setActionList($actions);
if ($content) { if ($is_draft) {
$draft_tag = id(new PHUITagView())
->setName(pht('Draft'))
->setIcon('fa-spinner')
->setColor('pink')
->setType(PHUITagView::TYPE_SHADE);
$header->addTag($draft_tag);
} else if ($content) {
$header->setEpoch($content->getDateCreated()); $header->setEpoch($content->getDateCreated());
} }
@ -227,25 +334,30 @@ final class PhrictionDocumentController
} }
$prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list); $prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list);
$page_content = id(new PHUIDocumentViewPro()) $page_content = id(new PHUIDocumentView())
->setBanner($version_note)
->setHeader($header) ->setHeader($header)
->setToc($toc) ->setToc($toc)
->appendChild( ->appendChild(
array( array(
$version_note,
$move_notice, $move_notice,
$core_content, $core_content,
)); ));
if ($curtain) {
$page_content->setCurtain($curtain);
}
return $this->newPage() return $this->newPage()
->setTitle($page_title) ->setTitle($page_title)
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->setPageObjectPHIDs(array($document->getPHID())) ->setPageObjectPHIDs(array($document->getPHID()))
->appendChild(array( ->appendChild(
$page_content, array(
$prop_list, $page_content,
$children, $prop_list,
)); $children,
));
} }
@ -257,8 +369,7 @@ final class PhrictionDocumentController
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$view = id(new PHUIPropertyListView()) $view = id(new PHUIPropertyListView())
->setUser($viewer) ->setUser($viewer);
->setObject($document);
$view->addProperty( $view->addProperty(
pht('Last Author'), pht('Last Author'),
@ -271,37 +382,70 @@ final class PhrictionDocumentController
return $view; return $view;
} }
private function buildActionView( private function buildCurtain(
PhabricatorUser $viewer, PhrictionDocument $document,
PhrictionDocument $document) { PhrictionContent $content) {
$viewer = $this->getViewer();
$can_edit = PhabricatorPolicyFilter::hasCapability( $can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer, $viewer,
$document, $document,
PhabricatorPolicyCapability::CAN_EDIT); PhabricatorPolicyCapability::CAN_EDIT);
$slug = PhabricatorSlug::normalize($this->slug); $slug = PhabricatorSlug::normalize($this->slug);
$id = $document->getID();
$action_view = id(new PhabricatorActionListView()) $curtain = $this->newCurtainView($document);
->setUser($viewer)
->setObject($document);
if (!$document->getID()) { $curtain->addAction(
return $action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create This Document'))
->setIcon('fa-plus-square')
->setHref('/phriction/edit/?slug='.$slug));
}
$action_view->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Edit Document')) ->setName(pht('Edit Document'))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setIcon('fa-pencil') ->setIcon('fa-pencil')
->setHref('/phriction/edit/'.$document->getID().'/')); ->setHref('/phriction/edit/'.$document->getID().'/'));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setIcon('fa-history')
->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
$is_current = false;
$content_id = null;
$is_draft = false;
if ($content) {
if ($content->getPHID() == $document->getContentPHID()) {
$is_current = true;
}
$content_id = $content->getID();
$current_version = $document->getContent()->getVersion();
$is_draft = ($content->getVersion() >= $current_version);
}
$can_publish = ($can_edit && $content && !$is_current);
if ($is_draft) {
$publish_name = pht('Publish Draft');
} else {
$publish_name = pht('Publish Revert');
}
$publish_uri = "/phriction/publish/{$id}/{$content_id}/";
if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
$publish_name = pht('Publish (Prototype!)');
$curtain->addAction(
id(new PhabricatorActionView())
->setName($publish_name)
->setIcon('fa-upload')
->setDisabled(!$can_publish)
->setWorkflow(true)
->setHref($publish_uri));
}
if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
$action_view->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Move Document')) ->setName(pht('Move Document'))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
@ -309,7 +453,7 @@ final class PhrictionDocumentController
->setHref('/phriction/move/'.$document->getID().'/') ->setHref('/phriction/move/'.$document->getID().'/')
->setWorkflow(true)); ->setWorkflow(true));
$action_view->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Delete Document')) ->setName(pht('Delete Document'))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
@ -318,23 +462,16 @@ final class PhrictionDocumentController
->setWorkflow(true)); ->setWorkflow(true));
} }
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setIcon('fa-list')
->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
$print_uri = PhrictionDocument::getSlugURI($slug).'?__print__=1'; $print_uri = PhrictionDocument::getSlugURI($slug).'?__print__=1';
$action_view->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Printable Page')) ->setName(pht('Printable Page'))
->setIcon('fa-print') ->setIcon('fa-print')
->setOpenInNewWindow(true) ->setOpenInNewWindow(true)
->setHref($print_uri)); ->setHref($print_uri));
return $action_view; return $curtain;
} }
private function renderDocumentChildren($slug) { private function renderDocumentChildren($slug) {

View file

@ -7,7 +7,7 @@ final class PhrictionEditController
$viewer = $request->getViewer(); $viewer = $request->getViewer();
$id = $request->getURIData('id'); $id = $request->getURIData('id');
$current_version = null; $max_version = null;
if ($id) { if ($id) {
$is_new = false; $is_new = false;
$document = id(new PhrictionDocumentQuery()) $document = id(new PhrictionDocumentQuery())
@ -24,7 +24,7 @@ final class PhrictionEditController
return new Aphront404Response(); return new Aphront404Response();
} }
$current_version = $document->getContent()->getVersion(); $max_version = $document->getMaxVersion();
$revert = $request->getInt('revert'); $revert = $request->getInt('revert');
if ($revert) { if ($revert) {
@ -37,9 +37,12 @@ final class PhrictionEditController
return new Aphront404Response(); return new Aphront404Response();
} }
} else { } else {
$content = $document->getContent(); $content = id(new PhrictionContentQuery())
->setViewer($viewer)
->withDocumentPHIDs(array($document->getPHID()))
->setLimit(1)
->executeOne();
} }
} else { } else {
$slug = $request->getStr('slug'); $slug = $request->getStr('slug');
$slug = PhabricatorSlug::normalize($slug); $slug = PhabricatorSlug::normalize($slug);
@ -54,8 +57,13 @@ final class PhrictionEditController
->executeOne(); ->executeOne();
if ($document) { if ($document) {
$content = $document->getContent(); $content = id(new PhrictionContentQuery())
$current_version = $content->getVersion(); ->setViewer($viewer)
->withDocumentPHIDs(array($document->getPHID()))
->setLimit(1)
->executeOne();
$max_version = $document->getMaxVersion();
$is_new = false; $is_new = false;
} else { } else {
$document = PhrictionDocument::initializeNewDocument($viewer, $slug); $document = PhrictionDocument::initializeNewDocument($viewer, $slug);
@ -128,7 +136,7 @@ final class PhrictionEditController
$title = $request->getStr('title'); $title = $request->getStr('title');
$content_text = $request->getStr('content'); $content_text = $request->getStr('content');
$notes = $request->getStr('description'); $notes = $request->getStr('description');
$current_version = $request->getInt('contentVersion'); $max_version = $request->getInt('contentVersion');
$v_view = $request->getStr('viewPolicy'); $v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy'); $v_edit = $request->getStr('editPolicy');
$v_cc = $request->getArr('cc'); $v_cc = $request->getArr('cc');
@ -168,7 +176,7 @@ final class PhrictionEditController
->setContinueOnNoEffect(true) ->setContinueOnNoEffect(true)
->setDescription($notes) ->setDescription($notes)
->setProcessContentVersionError(!$request->getBool('overwrite')) ->setProcessContentVersionError(!$request->getBool('overwrite'))
->setContentVersion($current_version); ->setContentVersion($max_version);
try { try {
$editor->applyTransactions($document, $xactions); $editor->applyTransactions($document, $xactions);
@ -226,19 +234,13 @@ final class PhrictionEditController
->execute(); ->execute();
$view_capability = PhabricatorPolicyCapability::CAN_VIEW; $view_capability = PhabricatorPolicyCapability::CAN_VIEW;
$edit_capability = PhabricatorPolicyCapability::CAN_EDIT; $edit_capability = PhabricatorPolicyCapability::CAN_EDIT;
$codex = id(PhabricatorPolicyCodex::newFromObject($document, $viewer))
->setCapability($view_capability);
$view_capability_description = $codex->getPolicySpecialRuleForCapability(
PhabricatorPolicyCapability::CAN_VIEW)->getDescription();
$edit_capability_description = $codex->getPolicySpecialRuleForCapability(
PhabricatorPolicyCapability::CAN_EDIT)->getDescription();
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($viewer) ->setUser($viewer)
->addHiddenInput('slug', $document->getSlug()) ->addHiddenInput('slug', $document->getSlug())
->addHiddenInput('nodraft', $request->getBool('nodraft')) ->addHiddenInput('nodraft', $request->getBool('nodraft'))
->addHiddenInput('contentVersion', $current_version) ->addHiddenInput('contentVersion', $max_version)
->addHiddenInput('overwrite', $overwrite) ->addHiddenInput('overwrite', $overwrite)
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
@ -279,15 +281,13 @@ final class PhrictionEditController
->setSpacePHID($v_space) ->setSpacePHID($v_space)
->setPolicyObject($document) ->setPolicyObject($document)
->setCapability($view_capability) ->setCapability($view_capability)
->setPolicies($policies) ->setPolicies($policies))
->setCaption($view_capability_description))
->appendChild( ->appendChild(
id(new AphrontFormPolicyControl()) id(new AphrontFormPolicyControl())
->setName('editPolicy') ->setName('editPolicy')
->setPolicyObject($document) ->setPolicyObject($document)
->setCapability($edit_capability) ->setCapability($edit_capability)
->setPolicies($policies) ->setPolicies($policies))
->setCaption($edit_capability_description))
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
->setLabel(pht('Edit Notes')) ->setLabel(pht('Edit Notes'))
@ -323,17 +323,17 @@ final class PhrictionEditController
$crumbs->setBorder(true); $crumbs->setBorder(true);
$view = id(new PHUITwoColumnView()) $view = id(new PHUITwoColumnView())
->setFooter(array( ->setFooter(
$draft_note, array(
$form_box, $draft_note,
$preview, $form_box,
)); $preview,
));
return $this->newPage() return $this->newPage()
->setTitle($page_title) ->setTitle($page_title)
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild($view); ->appendChild($view);
} }
} }

View file

@ -31,32 +31,18 @@ final class PhrictionHistoryController
->executeWithCursorPager($pager); ->executeWithCursorPager($pager);
$author_phids = mpull($history, 'getAuthorPHID'); $author_phids = mpull($history, 'getAuthorPHID');
$handles = $this->loadViewerHandles($author_phids); $handles = $viewer->loadHandles($author_phids);
$max_version = (int)$document->getMaxVersion();
$current_version = $document->getContent()->getVersion();
$list = new PHUIObjectItemListView(); $list = new PHUIObjectItemListView();
$list->setFlush(true); $list->setFlush(true);
foreach ($history as $content) { foreach ($history as $content) {
$author = $handles[$content->getAuthorPHID()]->renderLink();
$slug_uri = PhrictionDocument::getSlugURI($document->getSlug()); $slug_uri = PhrictionDocument::getSlugURI($document->getSlug());
$version = $content->getVersion(); $version = $content->getVersion();
$diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); $base_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/');
$vs_previous = null;
if ($content->getVersion() != 1) {
$vs_previous = $diff_uri
->alter('l', $content->getVersion() - 1)
->alter('r', $content->getVersion());
}
$vs_head = null;
if ($content->getID() != $document->getContentID()) {
$vs_head = $diff_uri
->alter('l', $content->getVersion())
->alter('r', $current->getVersion());
}
$change_type = PhrictionChangeType::getChangeTypeLabel( $change_type = PhrictionChangeType::getChangeTypeLabel(
$content->getChangeType()); $content->getChangeType());
@ -68,63 +54,90 @@ final class PhrictionHistoryController
$color = 'lightbluetext'; $color = 'lightbluetext';
break; break;
case PhrictionChangeType::CHANGE_MOVE_HERE: case PhrictionChangeType::CHANGE_MOVE_HERE:
$color = 'yellow'; $color = 'yellow';
break; break;
case PhrictionChangeType::CHANGE_MOVE_AWAY: case PhrictionChangeType::CHANGE_MOVE_AWAY:
$color = 'orange'; $color = 'orange';
break; break;
case PhrictionChangeType::CHANGE_STUB: case PhrictionChangeType::CHANGE_STUB:
$color = 'green'; $color = 'green';
break; break;
default: default:
throw new Exception(pht('Unknown change type!')); $color = 'indigo';
break; break;
} }
$version_uri = $slug_uri.'?v='.$version;
$item = id(new PHUIObjectItemView()) $item = id(new PHUIObjectItemView())
->setHeader(pht('%s by %s', $change_type, $author)) ->setHref($version_uri);
->setStatusIcon('fa-file '.$color)
->addAttribute(
phutil_tag(
'a',
array(
'href' => $slug_uri.'?v='.$version,
),
pht('Version %s', $version)))
->addAttribute(pht('%s %s',
phabricator_date($content->getDateCreated(), $viewer),
phabricator_time($content->getDateCreated(), $viewer)));
if ($content->getDescription()) { if ($version > $current_version) {
$item->addAttribute($content->getDescription()); $icon = 'fa-spinner';
} $color = 'pink';
$header = pht('Draft %d', $version);
if ($vs_previous) {
$item->addIcon(
'fa-reply',
pht('Show Change'),
array(
'href' => $vs_previous,
));
} else { } else {
$item->addIcon( $icon = 'fa-file-o';
'fa-reply grey', $header = pht('Version %d', $version);
phutil_tag('em', array(), pht('No previous change')));
} }
if ($vs_head) { if ($version == $current_version) {
$item->addIcon( $item->setEffect('selected');
'fa-reply-all',
pht('Show Later Changes'),
array(
'href' => $vs_head,
));
} else {
$item->addIcon(
'fa-reply-all grey',
phutil_tag('em', array(), pht('No later changes')));
} }
$item
->setHeader($header)
->setStatusIcon($icon.' '.$color);
$description = $content->getDescription();
if (strlen($description)) {
$item->addAttribute($description);
}
$item->addIcon(
null,
phabricator_datetime($content->getDateCreated(), $viewer));
$author_phid = $content->getAuthorPHID();
$item->addByline($viewer->renderHandle($author_phid));
$diff_uri = null;
if ($version > 1) {
$diff_uri = $base_uri
->alter('l', $version - 1)
->alter('r', $version);
} else {
$diff_uri = null;
}
if ($content->getVersion() != $max_version) {
$compare_uri = $base_uri
->alter('l', $version)
->alter('r', $max_version);
} else {
$compare_uri = null;
}
$button_bar = id(new PHUIButtonBarView())
->addButton(
id(new PHUIButtonView())
->setTag('a')
->setColor('grey')
->setIcon('fa-chevron-down')
->setDisabled(!$diff_uri)
->setHref($diff_uri)
->setText(pht('Diff')))
->addButton(
id(new PHUIButtonView())
->setTag('a')
->setColor('grey')
->setIcon('fa-chevron-circle-up')
->setDisabled(!$compare_uri)
->setHref($compare_uri)
->setText(pht('Compare')));
$item->setSideColumn($button_bar);
$list->addItem($item); $list->addItem($item);
} }

View file

@ -0,0 +1,86 @@
<?php
final class PhrictionPublishController
extends PhrictionController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('documentID');
$content_id = $request->getURIData('contentID');
$document = id(new PhrictionDocumentQuery())
->setViewer($viewer)
->withIDs(array($id))
->needContent(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
PhabricatorPolicyCapability::CAN_VIEW,
))
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$document_uri = $document->getURI();
$content = id(new PhrictionContentQuery())
->setViewer($viewer)
->withIDs(array($content_id))
->executeOne();
if (!$content) {
return new Aphront404Response();
}
if ($content->getPHID() == $document->getContentPHID()) {
return $this->newDialog()
->setTitle(pht('Already Published'))
->appendChild(
pht(
'This version of the document is already the published '.
'version.'))
->addCancelButton($document_uri);
}
$content_uri = $document_uri.'?v='.$content->getVersion();
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhrictionTransaction())
->setTransactionType(
PhrictionDocumentPublishTransaction::TRANSACTIONTYPE)
->setNewValue($content->getPHID());
id(new PhrictionTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($document, $xactions);
return id(new AphrontRedirectResponse())->setURI($document_uri);
}
if ($content->getVersion() < $document->getContent()->getVersion()) {
$title = pht('Revert Document?');
$body = pht(
'Revert the published version of this document to an older '.
'version?');
$button = pht('Revert');
} else {
$title = pht('Publish Draft?');
$body = pht(
'Update the published version of this document to this newer '.
'version?');
$button = pht('Publish');
}
return $this->newDialog()
->setTitle($title)
->appendChild($body)
->addSubmitButton($button)
->addCancelButton($content_uri);
}
}

View file

@ -93,29 +93,13 @@ final class PhrictionTransactionEditor
return $types; return $types;
} }
protected function shouldApplyInitialEffects( protected function expandTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE:
case PhrictionDocumentContentTransaction::TRANSACTIONTYPE:
case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE:
case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE:
case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE:
return true;
}
}
return parent::shouldApplyInitialEffects($object, $xactions);
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {
$this->setOldContent($object->getContent()); $this->setOldContent($object->getContent());
$this->setNewContent($this->buildNewContentTemplate($object));
return parent::expandTransactions($object, $xactions);
} }
protected function expandTransaction( protected function expandTransaction(
@ -148,7 +132,6 @@ final class PhrictionTransactionEditor
break; break;
default: default:
break; break;
} }
return $xactions; return $xactions;
@ -158,29 +141,12 @@ final class PhrictionTransactionEditor
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {
$save_content = false; if ($this->hasNewDocumentContent()) {
foreach ($xactions as $xaction) { $content = $this->getNewDocumentContent($object);
switch ($xaction->getTransactionType()) {
case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE:
case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE:
case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE:
case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE:
case PhrictionDocumentContentTransaction::TRANSACTIONTYPE:
$save_content = true;
break;
default:
break;
}
}
if ($save_content) { $content
$content = $this->getNewContent(); ->setDocumentPHID($object->getPHID())
$content->setDocumentID($object->getID()); ->save();
$content->save();
$object->setContentID($content->getID());
$object->save();
$object->attachContent($content);
} }
if ($this->getIsNewObject() && !$this->getSkipAncestorCheck()) { if ($this->getIsNewObject() && !$this->getSkipAncestorCheck()) {
@ -502,7 +468,7 @@ final class PhrictionTransactionEditor
$error = null; $error = null;
if ($this->getContentVersion() && if ($this->getContentVersion() &&
($object->getContent()->getVersion() != $this->getContentVersion())) { ($object->getMaxVersion() != $this->getContentVersion())) {
$error = new PhabricatorApplicationTransactionValidationError( $error = new PhabricatorApplicationTransactionValidationError(
$type, $type,
pht('Edit Conflict'), pht('Edit Conflict'),
@ -535,24 +501,48 @@ final class PhrictionTransactionEditor
->setDocument($object); ->setDocument($object);
} }
private function buildNewContentTemplate( private function hasNewDocumentContent() {
PhrictionDocument $document) { return (bool)$this->newContent;
}
$new_content = id(new PhrictionContent()) public function getNewDocumentContent(PhrictionDocument $document) {
if (!$this->hasNewDocumentContent()) {
$content = $this->newDocumentContent($document);
// Generate a PHID now so we can populate "contentPHID" before saving
// the document to the database: the column is not nullable so we need
// a value.
$content_phid = $content->generatePHID();
$content->setPHID($content_phid);
$document->setContentPHID($content_phid);
$document->attachContent($content);
$document->setEditedEpoch(PhabricatorTime::getNow());
$document->setMaxVersion($content->getVersion());
$this->newContent = $content;
}
return $this->newContent;
}
private function newDocumentContent(PhrictionDocument $document) {
$content = id(new PhrictionContent())
->setSlug($document->getSlug()) ->setSlug($document->getSlug())
->setAuthorPHID($this->getActor()->getPHID()) ->setAuthorPHID($this->getActingAsPHID())
->setChangeType(PhrictionChangeType::CHANGE_EDIT) ->setChangeType(PhrictionChangeType::CHANGE_EDIT)
->setTitle($this->getOldContent()->getTitle()) ->setTitle($this->getOldContent()->getTitle())
->setContent($this->getOldContent()->getContent()) ->setContent($this->getOldContent()->getContent())
->setDescription(''); ->setDescription('');
if (strlen($this->getDescription())) { if (strlen($this->getDescription())) {
$new_content->setDescription($this->getDescription()); $content->setDescription($this->getDescription());
} }
$new_content->setVersion($this->getOldContent()->getVersion() + 1); $content->setVersion($document->getMaxVersion() + 1);
return $new_content; return $content;
} }
protected function getCustomWorkerState() { protected function getCustomWorkerState() {

View file

@ -76,7 +76,7 @@ final class PhrictionContentQuery
if ($this->shouldJoinDocumentTable()) { if ($this->shouldJoinDocumentTable()) {
$joins[] = qsprintf( $joins[] = qsprintf(
$conn, $conn,
'JOIN %T d ON d.id = c.documentID', 'JOIN %T d ON d.phid = c.documentPHID',
id(new PhrictionDocument())->getTableName()); id(new PhrictionDocument())->getTableName());
} }
@ -84,19 +84,19 @@ final class PhrictionContentQuery
} }
protected function willFilterPage(array $contents) { protected function willFilterPage(array $contents) {
$document_ids = mpull($contents, 'getDocumentID'); $document_phids = mpull($contents, 'getDocumentPHID');
$documents = id(new PhrictionDocumentQuery()) $documents = id(new PhrictionDocumentQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->setParentQuery($this) ->setParentQuery($this)
->withIDs($document_ids) ->withPHIDs($document_phids)
->execute(); ->execute();
$documents = mpull($documents, null, 'getID'); $documents = mpull($documents, null, 'getPHID');
foreach ($contents as $key => $content) { foreach ($contents as $key => $content) {
$document_id = $content->getDocumentID(); $document_phid = $content->getDocumentPHID();
$document = idx($documents, $document_id); $document = idx($documents, $document_phid);
if (!$document) { if (!$document) {
unset($contents[$key]); unset($contents[$key]);
$this->didRejectResult($content); $this->didRejectResult($content);

View file

@ -151,17 +151,17 @@ final class PhrictionDocumentQuery
$contents = id(new PhrictionContentQuery()) $contents = id(new PhrictionContentQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->setParentQuery($this) ->setParentQuery($this)
->withIDs(mpull($documents, 'getContentID')) ->withPHIDs(mpull($documents, 'getContentPHID'))
->execute(); ->execute();
$contents = mpull($contents, null, 'getID'); $contents = mpull($contents, null, 'getPHID');
foreach ($documents as $key => $document) { foreach ($documents as $key => $document) {
$content_id = $document->getContentID(); $content_phid = $document->getContentPHID();
if (empty($contents[$content_id])) { if (empty($contents[$content_phid])) {
unset($documents[$key]); unset($documents[$key]);
continue; continue;
} }
$document->attachContent($contents[$content_id]); $document->attachContent($contents[$content_phid]);
} }
} }
@ -175,7 +175,7 @@ final class PhrictionDocumentQuery
$content_dao = new PhrictionContent(); $content_dao = new PhrictionContent();
$joins[] = qsprintf( $joins[] = qsprintf(
$conn, $conn,
'JOIN %T c ON d.contentID = c.id', 'JOIN %T c ON d.contentPHID = c.phid',
$content_dao->getTableName()); $content_dao->getTableName());
} }
@ -321,7 +321,7 @@ final class PhrictionDocumentQuery
public function getBuiltinOrders() { public function getBuiltinOrders() {
return parent::getBuiltinOrders() + array( return parent::getBuiltinOrders() + array(
self::ORDER_HIERARCHY => array( self::ORDER_HIERARCHY => array(
'vector' => array('depth', 'title', 'updated'), 'vector' => array('depth', 'title', 'updated', 'id'),
'name' => pht('Hierarchy'), 'name' => pht('Hierarchy'),
), ),
); );
@ -343,9 +343,9 @@ final class PhrictionDocumentQuery
), ),
'updated' => array( 'updated' => array(
'table' => 'd', 'table' => 'd',
'column' => 'contentID', 'column' => 'editedEpoch',
'type' => 'int', 'type' => 'int',
'unique' => true, 'unique' => false,
), ),
); );
} }
@ -356,7 +356,7 @@ final class PhrictionDocumentQuery
$map = array( $map = array(
'id' => $document->getID(), 'id' => $document->getID(),
'depth' => $document->getDepth(), 'depth' => $document->getDepth(),
'updated' => $document->getContentID(), 'updated' => $document->getEditedEpoch(),
); );
foreach ($keys as $key) { foreach ($keys as $key) {

View file

@ -7,7 +7,7 @@ final class PhrictionContent
PhabricatorDestructibleInterface, PhabricatorDestructibleInterface,
PhabricatorConduitResultInterface { PhabricatorConduitResultInterface {
protected $documentID; protected $documentPHID;
protected $version; protected $version;
protected $authorPHID; protected $authorPHID;
@ -34,8 +34,8 @@ final class PhrictionContent
'description' => 'text', 'description' => 'text',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'documentID' => array( 'key_version' => array(
'columns' => array('documentID', 'version'), 'columns' => array('documentPHID', 'version'),
'unique' => true, 'unique' => true,
), ),
'authorPHID' => array( 'authorPHID' => array(

View file

@ -17,12 +17,13 @@ final class PhrictionDocument extends PhrictionDAO
protected $slug; protected $slug;
protected $depth; protected $depth;
protected $contentID; protected $contentPHID;
protected $status; protected $status;
protected $mailKey;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
protected $spacePHID; protected $spacePHID;
protected $editedEpoch;
protected $maxVersion;
private $contentObject = self::ATTACHABLE; private $contentObject = self::ATTACHABLE;
private $ancestors = array(); private $ancestors = array();
@ -34,16 +35,11 @@ final class PhrictionDocument extends PhrictionDAO
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'slug' => 'sort128', 'slug' => 'sort128',
'depth' => 'uint32', 'depth' => 'uint32',
'contentID' => 'id?',
'status' => 'text32', 'status' => 'text32',
'mailKey' => 'bytes20', 'editedEpoch' => 'epoch',
'maxVersion' => 'uint32',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'slug' => array( 'slug' => array(
'columns' => array('slug'), 'columns' => array('slug'),
'unique' => true, 'unique' => true,
@ -56,17 +52,16 @@ final class PhrictionDocument extends PhrictionDAO
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID( return PhrictionDocumentPHIDType::TYPECONST;
PhrictionDocumentPHIDType::TYPECONST);
} }
public static function initializeNewDocument(PhabricatorUser $actor, $slug) { public static function initializeNewDocument(PhabricatorUser $actor, $slug) {
$document = new PhrictionDocument(); $document = id(new self())
$document->setSlug($slug); ->setSlug($slug);
$content = new PhrictionContent(); $content = id(new PhrictionContent())
$content->setSlug($slug); ->setSlug($slug);
$default_title = PhabricatorSlug::getDefaultTitle($slug); $default_title = PhabricatorSlug::getDefaultTitle($slug);
$content->setTitle($default_title); $content->setTitle($default_title);
@ -95,14 +90,10 @@ final class PhrictionDocument extends PhrictionDAO
->setSpacePHID($actor->getDefaultSpacePHID()); ->setSpacePHID($actor->getDefaultSpacePHID());
} }
return $document; $document->setEditedEpoch(PhabricatorTime::getNow());
} $document->setMaxVersion(0);
public function save() { return $document;
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
} }
public static function getSlugURI($slug, $type = 'document') { public static function getSlugURI($slug, $type = 'document') {
@ -332,9 +323,9 @@ final class PhrictionDocument extends PhrictionDAO
/* -( PhabricatorPolicyCodexInterface )------------------------------------ */ /* -( PhabricatorPolicyCodexInterface )------------------------------------ */
public function newPolicyCodex() { public function newPolicyCodex() {
return new PhrictionDocumentPolicyCodex(); return new PhrictionDocumentPolicyCodex();
} }
} }

View file

@ -1,7 +1,7 @@
<?php <?php
final class PhrictionDocumentContentTransaction final class PhrictionDocumentContentTransaction
extends PhrictionDocumentTransactionType { extends PhrictionDocumentVersionTransaction {
const TRANSACTIONTYPE = 'content'; const TRANSACTIONTYPE = 'content';
@ -18,10 +18,9 @@ final class PhrictionDocumentContentTransaction
public function applyInternalEffects($object, $value) { public function applyInternalEffects($object, $value) {
$object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS);
}
public function applyExternalEffects($object, $value) { $content = $this->getNewDocumentContent($object);
$this->getEditor()->getNewContent()->setContent($value); $content->setContent($value);
} }
public function shouldHide() { public function shouldHide() {

View file

@ -1,7 +1,7 @@
<?php <?php
final class PhrictionDocumentDeleteTransaction final class PhrictionDocumentDeleteTransaction
extends PhrictionDocumentTransactionType { extends PhrictionDocumentVersionTransaction {
const TRANSACTIONTYPE = 'delete'; const TRANSACTIONTYPE = 'delete';
@ -11,12 +11,11 @@ final class PhrictionDocumentDeleteTransaction
public function applyInternalEffects($object, $value) { public function applyInternalEffects($object, $value) {
$object->setStatus(PhrictionDocumentStatus::STATUS_DELETED); $object->setStatus(PhrictionDocumentStatus::STATUS_DELETED);
}
public function applyExternalEffects($object, $value) { $content = $this->getNewDocumentContent($object);
$this->getEditor()->getNewContent()->setContent('');
$this->getEditor()->getNewContent()->setChangeType( $content->setContent('');
PhrictionChangeType::CHANGE_DELETE); $content->setChangeType(PhrictionChangeType::CHANGE_DELETE);
} }
public function getActionStrength() { public function getActionStrength() {

View file

@ -1,7 +1,7 @@
<?php <?php
final class PhrictionDocumentMoveAwayTransaction final class PhrictionDocumentMoveAwayTransaction
extends PhrictionDocumentTransactionType { extends PhrictionDocumentVersionTransaction {
const TRANSACTIONTYPE = 'move-away'; const TRANSACTIONTYPE = 'move-away';
@ -22,14 +22,12 @@ final class PhrictionDocumentMoveAwayTransaction
public function applyInternalEffects($object, $value) { public function applyInternalEffects($object, $value) {
$object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED);
}
public function applyExternalEffects($object, $value) { $content = $this->getNewDocumentContent($object);
$dict = $value;
$this->getEditor()->getNewContent()->setContent(''); $content->setContent('');
$this->getEditor()->getNewContent()->setChangeType( $content->setChangeType(PhrictionChangeType::CHANGE_MOVE_AWAY);
PhrictionChangeType::CHANGE_MOVE_AWAY); $content->setChangeRef($value['id']);
$this->getEditor()->getNewContent()->setChangeRef($dict['id']);
} }
public function getActionName() { public function getActionName() {

View file

@ -1,7 +1,7 @@
<?php <?php
final class PhrictionDocumentMoveToTransaction final class PhrictionDocumentMoveToTransaction
extends PhrictionDocumentTransactionType { extends PhrictionDocumentVersionTransaction {
const TRANSACTIONTYPE = 'move-to'; const TRANSACTIONTYPE = 'move-to';
@ -11,6 +11,7 @@ final class PhrictionDocumentMoveToTransaction
public function generateNewValue($object, $value) { public function generateNewValue($object, $value) {
$document = $value; $document = $value;
$dict = array( $dict = array(
'id' => $document->getID(), 'id' => $document->getID(),
'phid' => $document->getPHID(), 'phid' => $document->getPHID(),
@ -26,15 +27,13 @@ final class PhrictionDocumentMoveToTransaction
public function applyInternalEffects($object, $value) { public function applyInternalEffects($object, $value) {
$object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS);
}
public function applyExternalEffects($object, $value) { $content = $this->getNewDocumentContent($object);
$dict = $value;
$this->getEditor()->getNewContent()->setContent($dict['content']); $content->setContent($value['content']);
$this->getEditor()->getNewContent()->setTitle($dict['title']); $content->setTitle($value['title']);
$this->getEditor()->getNewContent()->setChangeType( $content->setChangeType(PhrictionChangeType::CHANGE_MOVE_HERE);
PhrictionChangeType::CHANGE_MOVE_HERE); $content->setChangeRef($value['id']);
$this->getEditor()->getNewContent()->setChangeRef($dict['id']);
} }
public function getActionStrength() { public function getActionStrength() {

View file

@ -0,0 +1,71 @@
<?php
final class PhrictionDocumentPublishTransaction
extends PhrictionDocumentTransactionType {
const TRANSACTIONTYPE = 'publish';
public function generateOldValue($object) {
return $object->getContentPHID();
}
public function applyInternalEffects($object, $value) {
$object->setContentPHID($value);
}
public function getActionName() {
return pht('Published');
}
public function getTitle() {
return pht(
'%s published a new version of this document.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s published a new version of %s.',
$this->renderAuthor(),
$this->renderObject());
}
public function validateTransactions($object, array $xactions) {
$actor = $this->getActor();
$errors = array();
foreach ($xactions as $xaction) {
$content_phid = $xaction->getNewValue();
// If this isn't changing anything, skip it.
if ($content_phid === $object->getContentPHID()) {
continue;
}
$content = id(new PhrictionContentQuery())
->setViewer($actor)
->withPHIDs(array($content_phid))
->executeOne();
if (!$content) {
$errors[] = $this->newInvalidError(
pht(
'Unable to load Content object with PHID "%s".',
$content_phid),
$xaction);
continue;
}
if ($content->getDocumentPHID() !== $object->getPHID()) {
$errors[] = $this->newInvalidError(
pht(
'Content object "%s" can not be published because it belongs '.
'to a different document.',
$content_phid));
continue;
}
}
return $errors;
}
}

View file

@ -1,7 +1,7 @@
<?php <?php
final class PhrictionDocumentTitleTransaction final class PhrictionDocumentTitleTransaction
extends PhrictionDocumentTransactionType { extends PhrictionDocumentVersionTransaction {
const TRANSACTIONTYPE = 'title'; const TRANSACTIONTYPE = 'title';
@ -14,10 +14,10 @@ final class PhrictionDocumentTitleTransaction
public function applyInternalEffects($object, $value) { public function applyInternalEffects($object, $value) {
$object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS);
}
public function applyExternalEffects($object, $value) { $content = $this->getNewDocumentContent($object);
$this->getEditor()->getNewContent()->setTitle($value);
$content->setTitle($value);
} }
public function getActionStrength() { public function getActionStrength() {

View file

@ -0,0 +1,10 @@
<?php
abstract class PhrictionDocumentVersionTransaction
extends PhrictionDocumentTransactionType {
protected function getNewDocumentContent($object) {
return $this->getEditor()->getNewDocumentContent($object);
}
}

View file

@ -115,58 +115,6 @@ final class PhabricatorProjectTransactionEditor
return $errors; return $errors;
} }
protected function requireCapabilities(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_EDGE:
switch ($xaction->getMetadataValue('edge:type')) {
case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST:
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$add = array_keys(array_diff_key($new, $old));
$rem = array_keys(array_diff_key($old, $new));
$actor_phid = $this->requireActor()->getPHID();
$is_join = (($add === array($actor_phid)) && !$rem);
$is_leave = (($rem === array($actor_phid)) && !$add);
if ($is_join) {
// You need CAN_JOIN to join a project.
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_JOIN);
} else if ($is_leave) {
// You usually don't need any capabilities to leave a project.
if ($object->getIsMembershipLocked()) {
// you must be able to edit though to leave locked projects
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
}
} else {
if (!$this->getIsNewObject()) {
// You need CAN_EDIT to change members other than yourself.
// (PHI193) Just skip this check if we're creating a project.
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
}
}
return;
}
break;
}
return parent::requireCapabilities($object, $xaction);
}
protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { protected function willPublish(PhabricatorLiskDAO $object, array $xactions) {
// NOTE: We're using the omnipotent user here because the original actor // NOTE: We're using the omnipotent user here because the original actor
// may no longer have permission to view the object. // may no longer have permission to view the object.

View file

@ -200,6 +200,8 @@ final class PhabricatorRepositoryCommit
$this->authorIdentity = $author; $this->authorIdentity = $author;
$this->committerIdentity = $committer; $this->committerIdentity = $committer;
return $this;
} }
public function getAuthorIdentity() { public function getAuthorIdentity() {
@ -485,6 +487,23 @@ final class PhabricatorRepositoryCommit
return null; return null;
} }
public function loadIdentities(PhabricatorUser $viewer) {
if ($this->authorIdentity !== self::ATTACHABLE) {
return $this;
}
$commit = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withIDs(array($this->getID()))
->needIdentities(true)
->executeOne();
$author_identity = $commit->getAuthorIdentity();
$committer_identity = $commit->getCommitterIdentity();
return $this->attachIdentities($author_identity, $committer_identity);
}
public function hasCommitterIdentity() { public function hasCommitterIdentity() {
return ($this->getCommitterIdentity() !== null); return ($this->getCommitterIdentity() !== null);
} }

View file

@ -65,6 +65,16 @@ final class PhabricatorRepositoryIdentity
$this->getIdentityNameEncoding()); $this->getIdentityNameEncoding());
} }
public function getIdentityEmailAddress() {
$address = new PhutilEmailAddress($this->getIdentityName());
return $address->getAddress();
}
public function getIdentityDisplayName() {
$address = new PhutilEmailAddress($this->getIdentityName());
return $address->getDisplayName();
}
public function getIdentityShortName() { public function getIdentityShortName() {
// TODO // TODO
return $this->getIdentityName(); return $this->getIdentityName();

View file

@ -44,8 +44,6 @@ final class PhabricatorSubscriptionsMuteController
); );
} }
$muted_type = PhabricatorMutedByEdgeType::EDGECONST;
$xaction = id($object->getApplicationTransactionTemplate()) $xaction = id($object->getApplicationTransactionTemplate())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $muted_type) ->setMetadataValue('edge:type', $muted_type)

View file

@ -1679,7 +1679,7 @@ abstract class PhabricatorEditEngine
->setUser($viewer) ->setUser($viewer)
->setFields($fields); ->setFields($fields);
$document = id(new PHUIDocumentViewPro()) $document = id(new PHUIDocumentView())
->setUser($viewer) ->setUser($viewer)
->setHeader($header) ->setHeader($header)
->appendChild($help_view); ->appendChild($help_view);
@ -2003,7 +2003,19 @@ abstract class PhabricatorEditEngine
$identifier = $request->getValue('objectIdentifier'); $identifier = $request->getValue('objectIdentifier');
if ($identifier) { if ($identifier) {
$this->setIsCreate(false); $this->setIsCreate(false);
$object = $this->newObjectFromIdentifier($identifier);
// After T13186, each transaction can individually weaken or replace the
// capabilities required to apply it, so we no longer need CAN_EDIT to
// attempt to apply transactions to objects. In practice, almost all
// transactions require CAN_EDIT so we won't get very far if we don't
// have it.
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
);
$object = $this->newObjectFromIdentifier(
$identifier,
$capabilities);
} else { } else {
$this->requireCreateCapability(); $this->requireCreateCapability();

View file

@ -976,6 +976,27 @@ abstract class PhabricatorApplicationTransactionEditor
$this->adjustTransactionValues($object, $xaction); $this->adjustTransactionValues($object, $xaction);
} }
// Now that we've merged and combined transactions, check for required
// capabilities. Note that we're doing this before filtering
// transactions: if you try to apply an edit which you do not have
// permission to apply, we want to give you a permissions error even
// if the edit would have no effect.
$this->applyCapabilityChecks($object, $xactions);
// See T13186. Fatal hard if this object has an older
// "requireCapabilities()" method. The code may rely on this method being
// called to apply policy checks, so err on the side of safety and fatal.
// TODO: Remove this check after some time has passed.
if (method_exists($this, 'requireCapabilities')) {
throw new Exception(
pht(
'Editor (of class "%s") implements obsolete policy method '.
'requireCapabilities(). The implementation for this Editor '.
'MUST be updated. See <%s> for discussion.',
get_class($this),
'https://secure.phabricator.com/T13186'));
}
$xactions = $this->filterTransactions($object, $xactions); $xactions = $this->filterTransactions($object, $xactions);
// TODO: Once everything is on EditEngine, just use getIsNewObject() to // TODO: Once everything is on EditEngine, just use getIsNewObject() to
@ -994,12 +1015,6 @@ abstract class PhabricatorApplicationTransactionEditor
} }
} }
// Now that we've merged, filtered, and combined transactions, check for
// required capabilities.
foreach ($xactions as $xaction) {
$this->requireCapabilities($object, $xaction);
}
$xactions = $this->sortTransactions($xactions); $xactions = $this->sortTransactions($xactions);
$file_phids = $this->extractFilePHIDs($object, $xactions); $file_phids = $this->extractFilePHIDs($object, $xactions);
@ -1459,34 +1474,151 @@ abstract class PhabricatorApplicationTransactionEditor
} }
} }
protected function requireCapabilities( private function applyCapabilityChecks(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) { array $xactions) {
assert_instances_of($xactions, 'PhabricatorApplicationTransaction');
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
if ($this->getIsNewObject()) { if ($this->getIsNewObject()) {
return; // If we're creating a new object, we don't need any special capabilities
// on the object. The actor has already made it through creation checks,
// and objects which haven't been created yet often can not be
// meaningfully tested for capabilities anyway.
$required_capabilities = array();
} else {
if (!$xactions && !$this->xactions) {
// If we aren't doing anything, require CAN_EDIT to improve consistency.
$required_capabilities = array($can_edit);
} else {
$required_capabilities = array();
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
$xtype = $this->getModularTransactionType($type);
if (!$xtype) {
$capabilities = $this->getLegacyRequiredCapabilities($xaction);
} else {
$capabilities = $xtype->getRequiredCapabilities($object, $xaction);
}
// For convenience, we allow flexibility in the return types because
// it's very unusual that a transaction actually requires multiple
// capability checks.
if ($capabilities === null) {
$capabilities = array();
} else {
$capabilities = (array)$capabilities;
}
foreach ($capabilities as $capability) {
$required_capabilities[$capability] = $capability;
}
}
}
} }
$actor = $this->requireActor(); $required_capabilities = array_fuse($required_capabilities);
switch ($xaction->getTransactionType()) { $actor = $this->getActor();
case PhabricatorTransactions::TYPE_COMMENT:
PhabricatorPolicyFilter::requireCapability( if ($required_capabilities) {
$actor, id(new PhabricatorPolicyFilter())
$object, ->setViewer($actor)
PhabricatorPolicyCapability::CAN_VIEW); ->requireCapabilities($required_capabilities)
break; ->raisePolicyExceptions(true)
case PhabricatorTransactions::TYPE_VIEW_POLICY: ->apply(array($object));
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
PhabricatorPolicyFilter::requireCapability(
$actor,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
} }
} }
private function getLegacyRequiredCapabilities(
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorTransactions::TYPE_COMMENT:
// TODO: Comments technically require CAN_INTERACT, but this is
// currently somewhat special and handled through EditEngine. For now,
// don't enforce it here.
return null;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
// TODO: Removing subscribers other than yourself should probably
// require CAN_EDIT permission. You can do this via the API but
// generally can not via the web interface.
return null;
case PhabricatorTransactions::TYPE_TOKEN:
// TODO: This technically requires CAN_INTERACT, like comments.
return null;
case PhabricatorTransactions::TYPE_HISTORY:
// This is a special magic transaction which sends you history via
// email and is only partially supported in the upstream. You don't
// need any capabilities to apply it.
return null;
case PhabricatorTransactions::TYPE_EDGE:
return $this->getLegacyRequiredEdgeCapabilities($xaction);
default:
// For other older (non-modular) transactions, always require exactly
// CAN_EDIT. Transactions which do not need CAN_EDIT or need additional
// capabilities must move to ModularTransactions.
return PhabricatorPolicyCapability::CAN_EDIT;
}
}
private function getLegacyRequiredEdgeCapabilities(
PhabricatorApplicationTransaction $xaction) {
// You don't need to have edit permission on an object to mention it or
// otherwise add a relationship pointing toward it.
if ($this->getIsInverseEdgeEditor()) {
return null;
}
$edge_type = $xaction->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorMutedByEdgeType::EDGECONST:
// At time of writing, you can only write this edge for yourself, so
// you don't need permissions. If you can eventually mute an object
// for other users, this would need to be revisited.
return null;
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
return null;
case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST:
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$add = array_keys(array_diff_key($new, $old));
$rem = array_keys(array_diff_key($old, $new));
$actor_phid = $this->requireActor()->getPHID();
$is_join = (($add === array($actor_phid)) && !$rem);
$is_leave = (($rem === array($actor_phid)) && !$add);
if ($is_join) {
// You need CAN_JOIN to join a project.
return PhabricatorPolicyCapability::CAN_JOIN;
}
if ($is_leave) {
$object = $this->object;
// You usually don't need any capabilities to leave a project...
if ($object->getIsMembershipLocked()) {
// ...you must be able to edit to leave locked projects, though.
return PhabricatorPolicyCapability::CAN_EDIT;
} else {
return null;
}
}
// You need CAN_EDIT to change members other than yourself.
return PhabricatorPolicyCapability::CAN_EDIT;
default:
return PhabricatorPolicyCapability::CAN_EDIT;
}
}
private function buildSubscribeTransaction( private function buildSubscribeTransaction(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions, array $xactions,

View file

@ -366,4 +366,30 @@ abstract class PhabricatorModularTransactionType
$capability); $capability);
} }
/**
* Get a list of capabilities the actor must have on the object to apply
* a transaction to it.
*
* Usually, you should use this to reduce capability requirements when a
* transaction (like leaving a Conpherence thread) can be applied without
* having edit permission on the object. You can override this method to
* remove the CAN_EDIT requirement, or to replace it with a different
* requirement.
*
* If you are increasing capability requirements and need to add an
* additional capability or policy requirement above and beyond CAN_EDIT, it
* is usually better implemented as a validation check.
*
* @param object Object being edited.
* @param PhabricatorApplicationTransaction Transaction being applied.
* @return null|const|list<const> A capability constant (or list of
* capability constants) which the actor must have on the object. You can
* return `null` as a shorthand for "no capabilities are required".
*/
public function getRequiredCapabilities(
$object,
PhabricatorApplicationTransaction $xaction) {
return PhabricatorPolicyCapability::CAN_EDIT;
}
} }

View file

@ -142,7 +142,7 @@ final class PhabricatorTypeaheadFunctionHelpController
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader($title); ->setHeader($title);
$document = id(new PHUIDocumentViewPro()) $document = id(new PHUIDocumentView())
->setHeader($header) ->setHeader($header)
->appendChild($content_box); ->appendChild($content_box);

View file

@ -279,7 +279,6 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
->setIcon($icon); ->setIcon($icon);
} }
if ($this->templatePHIDType) { if ($this->templatePHIDType) {
$context_path = 'template/'.$this->templatePHIDType.'/'; $context_path = 'template/'.$this->templatePHIDType.'/';
} else { } else {
@ -346,15 +345,6 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
)), )),
$input, $input,
)); ));
return AphrontFormSelectControl::renderSelectTag(
$this->getValue(),
$this->getOptions(),
array(
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
} }
public static function getSelectCustomKey() { public static function getSelectCustomKey() {

View file

@ -1,6 +1,6 @@
<?php <?php
final class PHUIDocumentViewPro extends AphrontTagView { final class PHUIDocumentView extends AphrontTagView {
private $header; private $header;
private $bookname; private $bookname;
@ -8,6 +8,8 @@ final class PHUIDocumentViewPro extends AphrontTagView {
private $fluid; private $fluid;
private $toc; private $toc;
private $foot; private $foot;
private $curtain;
private $banner;
public function setHeader(PHUIHeaderView $header) { public function setHeader(PHUIHeaderView $header) {
$header->setTall(true); $header->setTall(true);
@ -36,6 +38,24 @@ final class PHUIDocumentViewPro extends AphrontTagView {
return $this; return $this;
} }
public function setCurtain(PHUICurtainView $curtain) {
$this->curtain = $curtain;
return $this;
}
public function getCurtain() {
return $this->curtain;
}
public function setBanner($banner) {
$this->banner = $banner;
return $this;
}
public function getBanner() {
return $this->banner;
}
protected function getTagAttributes() { protected function getTagAttributes() {
$classes = array(); $classes = array();
@ -61,6 +81,17 @@ final class PHUIDocumentViewPro extends AphrontTagView {
$classes[] = 'phui-document-view'; $classes[] = 'phui-document-view';
$classes[] = 'phui-document-view-pro'; $classes[] = 'phui-document-view-pro';
if ($this->curtain) {
$classes[] = 'has-curtain';
} else {
$classes[] = 'has-no-curtain';
}
if ($this->curtain) {
$action_list = $this->curtain->getActionList();
$this->header->setActionListID($action_list->getID());
}
$book = null; $book = null;
if ($this->bookname) { if ($this->bookname) {
$book = pht('%s (%s)', $this->bookname, $this->bookdescription); $book = pht('%s (%s)', $this->bookname, $this->bookdescription);
@ -114,32 +145,52 @@ final class PHUIDocumentViewPro extends AphrontTagView {
$this->foot); $this->foot);
} }
$content_inner = phutil_tag( $curtain = null;
if ($this->curtain) {
$curtain = phutil_tag(
'div', 'div',
array( array(
'class' => 'phui-document-inner', 'class' => 'phui-document-curtain',
), ),
$this->curtain);
}
$main_content = phutil_tag(
'div',
array(
'class' => 'phui-document-content-view',
),
$main_content);
$content_inner = phutil_tag(
'div',
array(
'class' => 'phui-document-inner',
),
array(
$table_of_contents,
$this->header,
$this->banner,
array( array(
$table_of_contents, $curtain,
$this->header,
$main_content, $main_content,
$foot_content, ),
)); $foot_content,
));
$content = phutil_tag( $content = phutil_tag(
'div', 'div',
array( array(
'class' => 'phui-document-content', 'class' => 'phui-document-content',
), ),
$content_inner); $content_inner);
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$content);
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$content);
} }
} }

View file

@ -709,8 +709,9 @@ final class PHUIObjectItemView extends AphrontTagView {
} }
/* Fixed width, right column container. */ /* Fixed width, right column container. */
$column3 = null;
if ($this->sideColumn) { if ($this->sideColumn) {
$column2 = phutil_tag( $column3 = phutil_tag(
'div', 'div',
array( array(
'class' => 'phui-oi-col2 phui-oi-side-column', 'class' => 'phui-oi-col2 phui-oi-side-column',
@ -731,6 +732,7 @@ final class PHUIObjectItemView extends AphrontTagView {
$column0, $column0,
$column1, $column1,
$column2, $column2,
$column3,
))); )));
$box = phutil_tag( $box = phutil_tag(

View file

@ -105,7 +105,7 @@ final class PHUIRemarkupPreviewPanel extends AphrontTagView {
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader(pht('%s (Preview)', $this->header)); ->setHeader(pht('%s (Preview)', $this->header));
$content = id(new PHUIDocumentViewPro()) $content = id(new PHUIDocumentView())
->setHeader($header) ->setHeader($header)
->appendChild($preview); ->appendChild($preview);
} }

View file

@ -26,11 +26,14 @@
.harbormaster-unit-details { .harbormaster-unit-details {
margin: 8px 0 4px; margin: 8px 0 4px;
overflow: hidden; overflow: hidden;
white-space: pre;
text-overflow: ellipsis;
color: {$lightgreytext}; color: {$lightgreytext};
} }
.harbormaster-unit-details-text {
white-space: pre-wrap;
text-overflow: ellipsis;
}
.harbormaster-log-view-loading { .harbormaster-log-view-loading {
padding: 8px; padding: 8px;
text-align: center; text-align: center;

View file

@ -8,6 +8,35 @@
margin: 0 auto; margin: 0 auto;
} }
.phui-document-view.phui-document-view-pro.has-curtain {
max-width: 1132px;
}
.printable .phui-document-view.phui-document-view-pro.has-curtain {
max-width: none;
}
.device-desktop .phui-document-inner {
overflow: hidden;
}
.device-desktop .has-curtain .phui-document-content-view {
padding-right: 332px;
}
.printable .phui-document-content-view {
padding-right: 0;
}
.device-desktop .phui-document-curtain {
float: right;
width: 300px;
}
.printable .phui-document-curtain {
display: none;
}
.phui-document-container { .phui-document-container {
background-color: {$page.content}; background-color: {$page.content};
position: relative; position: relative;

View file

@ -63,7 +63,8 @@
font-size: 14px; font-size: 14px;
} }
.phui-document-view .phui-header-action-links .phui-mobile-menu { .phui-document-view.has-no-curtain
.phui-header-action-links .phui-mobile-menu {
display: block; display: block;
} }
@ -71,10 +72,6 @@
padding: 8px; padding: 8px;
} }
.device-desktop .phui-document-content .phabricator-action-list-view {
display: none;
}
.phui-document-content .phabricator-remarkup .remarkup-code-block { .phui-document-content .phabricator-remarkup .remarkup-code-block {
clear: both; clear: both;
margin: 16px 0; margin: 16px 0;
@ -103,3 +100,9 @@
.remarkup-code { .remarkup-code {
font: 13px/18px "Menlo", "Consolas", "Monaco", monospace; font: 13px/18px "Menlo", "Consolas", "Monaco", monospace;
} }
.phui-document-version-navigation {
text-align: center;
padding: 8px;
background-color: {$lightgreybackground};
}

View file

@ -259,7 +259,6 @@ body .phui-header-shell.phui-bleed-header
color: {$lightgreytext}; color: {$lightgreytext};
} }
.phui-header-action-links .phui-mobile-menu { .phui-header-action-links .phui-mobile-menu {
display: none; display: none;
} }

View file

@ -20,10 +20,13 @@ JX.behavior('phabricator-nav', function(config) {
// - Flexible Navigation Column ------------------------------------------------ // - Flexible Navigation Column ------------------------------------------------
var dragging; var dragging;
var track; var track;
var collapsed = config.collapsed;
var narrowed;
var visible = null;
JX.enableDispatch(document.body, 'mousemove'); JX.enableDispatch(document.body, 'mousemove');
JX.DOM.listen(drag, 'mousedown', null, function(e) { JX.DOM.listen(drag, 'mousedown', null, function(e) {
@ -95,6 +98,7 @@ JX.behavior('phabricator-nav', function(config) {
if (!dragging) { if (!dragging) {
return; return;
} }
JX.DOM.alterClass(document.body, 'jx-drag-col', false); JX.DOM.alterClass(document.body, 'jx-drag-col', false);
dragging = false; dragging = false;
@ -117,6 +121,29 @@ JX.behavior('phabricator-nav', function(config) {
return (JX.$V(drag).x - JX.Vector.getScroll().x); return (JX.$V(drag).x - JX.Vector.getScroll().x);
} }
function repaint() {
narrowed = !JX.Device.isDesktop();
var was_visible = visible;
visible = (!collapsed && !narrowed);
if (was_visible === visible) {
return;
}
if (!visible) {
savedrag();
}
JX.DOM.alterClass(main, 'has-local-nav', visible);
JX.DOM.alterClass(main, 'has-drag-nav', visible);
JX.DOM.alterClass(main, 'has-closed-nav', !visible);
if (visible) {
restoredrag();
}
}
var saved_width = config.width; var saved_width = config.width;
function savedrag() { function savedrag() {
saved_width = get_width(); saved_width = get_width();
@ -136,21 +163,10 @@ JX.behavior('phabricator-nav', function(config) {
content.style.marginLeft = (saved_width + JX.Vector.getDim(drag).x) + 'px'; content.style.marginLeft = (saved_width + JX.Vector.getDim(drag).x) + 'px';
} }
var collapsed = config.collapsed;
JX.Stratcom.listen('differential-filetree-toggle', null, function() { JX.Stratcom.listen('differential-filetree-toggle', null, function() {
collapsed = !collapsed; collapsed = !collapsed;
if (collapsed) { repaint();
savedrag();
}
JX.DOM.alterClass(main, 'has-local-nav', !collapsed);
JX.DOM.alterClass(main, 'has-drag-nav', !collapsed);
JX.DOM.alterClass(main, 'has-closed-nav', collapsed);
if (!collapsed) {
restoredrag();
}
new JX.Request('/settings/adjust/', JX.bag) new JX.Request('/settings/adjust/', JX.bag)
.setData({ key : 'nav-collapsed', value : (collapsed ? 1 : 0) }) .setData({ key : 'nav-collapsed', value : (collapsed ? 1 : 0) })
@ -168,7 +184,9 @@ JX.behavior('phabricator-nav', function(config) {
// of the navigation bar. // of the navigation bar.
function onresize() { function onresize() {
if (JX.Device.getDevice() != 'desktop') { repaint();
if (!visible) {
return; return;
} }
@ -193,14 +211,13 @@ JX.behavior('phabricator-nav', function(config) {
local.style.left = 0; local.style.left = 0;
JX.Stratcom.listen(['scroll', 'resize'], null, onresize); JX.Stratcom.listen(['scroll', 'resize'], null, onresize);
onresize();
repaint();
// - Navigation Reset ---------------------------------------------------------- // - Navigation Reset ----------------------------------------------------------
JX.Stratcom.listen('phabricator-device-change', null, function() { JX.Stratcom.listen('phabricator-device-change', null, function() {
resetdrag(); repaint();
onresize();
}); });
}); });