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

(stable) Promote 2016 Week 3

This commit is contained in:
epriestley 2016-01-15 09:09:36 -08:00
commit eac1124bd9
119 changed files with 3303 additions and 2995 deletions

View file

@ -1,105 +0,0 @@
AMAZON S3 PHP CLASS
USING THE CLASS
OO method (e,g; $s3->getObject(...)):
$s3 = new S3(awsAccessKey, awsSecretKey);
Statically (e,g; S3::getObject(...)):
S3::setAuth(awsAccessKey, awsSecretKey);
For class documentation see:
http://undesigned.org.za/files/s3-class-documentation/index.html
OBJECTS
Put an object from a string:
$s3->putObject($string, $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
Legacy function: $s3->putObjectString($string, $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
Put an object from a file:
$s3->putObject($s3->inputFile($file, false), $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
Legacy function: $s3->putObjectFile($uploadFile, $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
Put an object from a resource (buffer/file size is required):
Please note: the resource will be fclose()'d automatically
$s3->putObject($s3->inputResource(fopen($file, 'rb'), filesize($file)), $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
Get an object:
$s3->getObject($bucketName, $uploadName)
Save an object to file:
$s3->getObject($bucketName, $uploadName, $saveName)
Save an object to a resource of any type:
$s3->getObject($bucketName, $uploadName, fopen('savefile.txt', 'wb'))
Copy an object:
$s3->copyObject($srcBucket, $srcName, $bucketName, $saveName, $metaHeaders = array(), $requestHeaders = array())
Delete an object:
$s3->deleteObject($bucketName, $uploadName)
BUCKETS
Get a list of buckets:
$s3->listBuckets() // Simple bucket list
$s3->listBuckets(true) // Detailed bucket list
Create a public-read bucket:
$s3->putBucket($bucketName, S3::ACL_PUBLIC_READ)
$s3->putBucket($bucketName, S3::ACL_PUBLIC_READ, 'EU') // EU-hosted bucket
Get the contents of a bucket:
$s3->getBucket($bucketName)
Get a bucket's location:
$s3->getBucketLocation($bucketName)
Delete a bucket:
$s3->deleteBucket($bucketName)
KNOWN ISSUES
Files larger than 2GB are not supported on 32 bit systems due to PHPs signed integer problem
MORE INFORMATION
Project URL:
http://undesigned.org.za/2007/10/22/amazon-s3-php-class
Class documentation:
http://undesigned.org.za/files/s3-class-documentation/index.html
Bug reports:
https://github.com/tpyo/amazon-s3-php-class/issues
Amazon S3 documentation:
http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
EOF

2317
externals/s3/S3.php vendored

File diff suppressed because it is too large Load diff

View file

@ -7,11 +7,11 @@
*/ */
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => '3ea6dc33', 'core.pkg.css' => '1eed0b4f',
'core.pkg.js' => '57dff7df', 'core.pkg.js' => '6ae03393',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9', 'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => '64e69521', 'differential.pkg.js' => 'f83532f8',
'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.css' => 'f45955ed',
'diffusion.pkg.js' => '3a9a8bfa', 'diffusion.pkg.js' => '3a9a8bfa',
'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.css' => '4845691a',
@ -102,7 +102,7 @@ return array(
'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => 'a76cefc9', 'rsrc/css/core/core.css' => 'a76cefc9',
'rsrc/css/core/remarkup.css' => '7afb543c', 'rsrc/css/core/remarkup.css' => 'b6ad82e4',
'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/syntax.css' => '9fd11da8',
'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/core/z-index.css' => '57ddcaa2',
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
@ -375,13 +375,13 @@ return array(
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
'rsrc/js/application/differential/ChangesetViewManager.js' => '58562350', 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756',
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '64a5550f', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '64a5550f',
'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18', 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18',
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76',
'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1',
'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324',
'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '65ef6074', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '65ef6074',
'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492',
'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df',
@ -424,9 +424,10 @@ return array(
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8',
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43',
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
'rsrc/js/application/transactions/behavior-comment-actions.js' => 'b65559c0', 'rsrc/js/application/transactions/behavior-comment-actions.js' => '1f2fcaf8',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6',
@ -492,7 +493,7 @@ return array(
'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7',
'rsrc/js/core/behavior-scrollbar.js' => '834a1173', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173',
'rsrc/js/core/behavior-search-typeahead.js' => '048330fa', 'rsrc/js/core/behavior-search-typeahead.js' => '0b7a4f6e',
'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6',
'rsrc/js/core/behavior-time-typeahead.js' => 'f80d6bf0', 'rsrc/js/core/behavior-time-typeahead.js' => 'f80d6bf0',
'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33', 'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33',
@ -523,7 +524,7 @@ return array(
'aphront-typeahead-control-css' => '0e403212', 'aphront-typeahead-control-css' => '0e403212',
'auth-css' => '0877ed6e', 'auth-css' => '0877ed6e',
'bulk-job-css' => 'df9c1d4a', 'bulk-job-css' => 'df9c1d4a',
'changeset-view-manager' => '58562350', 'changeset-view-manager' => 'a2828756',
'conduit-api-css' => '7bc725c4', 'conduit-api-css' => '7bc725c4',
'config-options-css' => '0ede4c9b', 'config-options-css' => '0ede4c9b',
'config-welcome-css' => '6abd79be', 'config-welcome-css' => '6abd79be',
@ -570,7 +571,7 @@ return array(
'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-audit-preview' => 'd835b03a',
'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-bulk-job-reload' => 'edf8a145',
'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-choose-control' => '327a00d1',
'javelin-behavior-comment-actions' => 'b65559c0', 'javelin-behavior-comment-actions' => '1f2fcaf8',
'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-config-reorder-fields' => 'b6993408',
'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a',
'javelin-behavior-conpherence-menu' => '1d45c74d', 'javelin-behavior-conpherence-menu' => '1d45c74d',
@ -588,7 +589,7 @@ return array(
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-comment-jump' => '4fdb476d',
'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1',
'javelin-behavior-differential-dropdown-menus' => '2035b9cb', 'javelin-behavior-differential-dropdown-menus' => '9a6b9324',
'javelin-behavior-differential-edit-inline-comments' => '65ef6074', 'javelin-behavior-differential-edit-inline-comments' => '65ef6074',
'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-feedback-preview' => 'b064af76',
'javelin-behavior-differential-keyboard-navigation' => '2c426492', 'javelin-behavior-differential-keyboard-navigation' => '2c426492',
@ -640,7 +641,7 @@ return array(
'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-oncopy' => '2926fff2',
'javelin-behavior-phabricator-remarkup-assist' => 'b60b6d9b', 'javelin-behavior-phabricator-remarkup-assist' => 'b60b6d9b',
'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-reveal-content' => '60821bc7',
'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-search-typeahead' => '0b7a4f6e',
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
'javelin-behavior-phabricator-tooltips' => '3ee3408b', 'javelin-behavior-phabricator-tooltips' => '3ee3408b',
'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6',
@ -663,6 +664,7 @@ return array(
'javelin-behavior-remarkup-preview' => '4b700e9e', 'javelin-behavior-remarkup-preview' => '4b700e9e',
'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-applications' => '76b9fc3e',
'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-reorder-columns' => 'e1d25dfb',
'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072',
'javelin-behavior-repository-crossreference' => 'e5339c43', 'javelin-behavior-repository-crossreference' => 'e5339c43',
'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-scrollbar' => '834a1173',
'javelin-behavior-search-reorder-queries' => 'e9581f08', 'javelin-behavior-search-reorder-queries' => 'e9581f08',
@ -757,7 +759,7 @@ return array(
'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646', 'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '666c80c5', 'phabricator-prefab' => '666c80c5',
'phabricator-remarkup-css' => '7afb543c', 'phabricator-remarkup-css' => 'b6ad82e4',
'phabricator-search-results-css' => '7dea472c', 'phabricator-search-results-css' => '7dea472c',
'phabricator-shaped-request' => '7cbe244b', 'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => '91b7a42c', 'phabricator-side-menu-view-css' => '91b7a42c',
@ -878,16 +880,6 @@ return array(
'javelin-behavior-device', 'javelin-behavior-device',
'phabricator-title', 'phabricator-title',
), ),
'048330fa' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
'javelin-typeahead',
'javelin-dom',
'javelin-uri',
'javelin-util',
'javelin-stratcom',
'phabricator-prefab',
),
'05270951' => array( '05270951' => array(
'javelin-util', 'javelin-util',
'javelin-magical-init', 'javelin-magical-init',
@ -915,6 +907,16 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-router', 'javelin-router',
), ),
'0b7a4f6e' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
'javelin-typeahead',
'javelin-dom',
'javelin-uri',
'javelin-util',
'javelin-stratcom',
'phabricator-prefab',
),
'0f764c35' => array( '0f764c35' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@ -969,17 +971,14 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-reactor-dom', 'javelin-reactor-dom',
), ),
'2035b9cb' => array( '1f2fcaf8' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom', 'javelin-stratcom',
'javelin-workflow', 'javelin-workflow',
'phuix-dropdown-menu', 'javelin-dom',
'phuix-action-list-view', 'phuix-form-control-view',
'phuix-action-view', 'phuix-icon-view',
'phabricator-phtize', 'javelin-behavior-phabricator-gesture',
'changeset-view-manager',
), ),
'21ba5861' => array( '21ba5861' => array(
'javelin-behavior', 'javelin-behavior',
@ -1201,16 +1200,6 @@ return array(
'javelin-request', 'javelin-request',
'javelin-util', 'javelin-util',
), ),
58562350 => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
),
'59a7976a' => array( '59a7976a' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@ -1562,6 +1551,18 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-reactor-dom', 'javelin-reactor-dom',
), ),
'9a6b9324' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'phabricator-phtize',
'changeset-view-manager',
),
'9e54692d' => array( '9e54692d' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@ -1601,6 +1602,16 @@ return array(
'javelin-vector', 'javelin-vector',
'javelin-install', 'javelin-install',
), ),
'a2828756' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
),
'a464fe03' => array( 'a464fe03' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-uri', 'javelin-uri',
@ -1740,15 +1751,6 @@ return array(
'javelin-workflow', 'javelin-workflow',
'javelin-vector', 'javelin-vector',
), ),
'b65559c0' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phuix-form-control-view',
'phuix-icon-view',
'javelin-behavior-phabricator-gesture',
),
'b6993408' => array( 'b6993408' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1933,6 +1935,13 @@ return array(
'e292eaf4' => array( 'e292eaf4' => array(
'javelin-install', 'javelin-install',
), ),
'e2e0a072' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'e379b58e' => array( 'e379b58e' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_repository.repository
ADD repositorySlug VARCHAR(64) COLLATE {$COLLATE_SORT};
ALTER TABLE {$NAMESPACE}_repository.repository
ADD UNIQUE KEY `key_slug` (repositorySlug);

View file

@ -0,0 +1,49 @@
<?php
$table = new PhabricatorRepository();
$conn_w = $table->establishConnection('w');
foreach (new LiskMigrationIterator($table) as $repository) {
$slug = $repository->getRepositorySlug();
if ($slug !== null) {
continue;
}
$clone_name = $repository->getDetail('clone-name');
if (!strlen($clone_name)) {
continue;
}
if (!PhabricatorRepository::isValidRepositorySlug($clone_name)) {
echo tsprintf(
"%s\n",
pht(
'Repository "%s" has a "Clone/Checkout As" name which is no longer '.
'valid ("%s"). You can edit the repository to give it a new, valid '.
'short name.',
$repository->getDisplayName(),
$clone_name));
continue;
}
try {
queryfx(
$conn_w,
'UPDATE %T SET repositorySlug = %s WHERE id = %d',
$table->getTableName(),
$clone_name,
$repository->getID());
} catch (AphrontDuplicateKeyQueryException $ex) {
echo tsprintf(
"%s\n",
pht(
'Repository "%s" has a duplicate "Clone/Checkout As" name ("%s"). '.
'Each name must now be unique. You can edit the repository to give '.
'it a new, unique short name.',
$repository->getDisplayName(),
$clone_name));
}
}

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_repository.repository_transaction
SET transactionType = 'repo:slug' WHERE transactionType = 'repo:clone-name';

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_repository.repository_uriindex (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
repositoryPHID VARBINARY(64) NOT NULL,
repositoryURI LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_repository` (repositoryPHID),
KEY `key_uri` (repositoryURI(128))
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,7 @@
<?php
$table = new PhabricatorRepository();
foreach (new LiskMigrationIterator($table) as $repo) {
$repo->updateURIIndex();
}

View file

@ -0,0 +1,13 @@
CREATE TABLE {$NAMESPACE}_search.search_profilepanelconfiguration (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
profilePHID VARBINARY(64) NOT NULL,
panelKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT},
panelOrder INT UNSIGNED,
visibility VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
panelProperties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
KEY `key_profile` (profilePHID, panelOrder)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_search.search_profilepanelconfigurationtransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -249,6 +249,7 @@ phutil_register_library_map(array(
'ConduitStringParameterType' => 'applications/conduit/parametertype/ConduitStringParameterType.php', 'ConduitStringParameterType' => 'applications/conduit/parametertype/ConduitStringParameterType.php',
'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php', 'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php',
'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php', 'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php',
'ConduitUserParameterType' => 'applications/conduit/parametertype/ConduitUserParameterType.php',
'ConduitWildParameterType' => 'applications/conduit/parametertype/ConduitWildParameterType.php', 'ConduitWildParameterType' => 'applications/conduit/parametertype/ConduitWildParameterType.php',
'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php', 'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php',
'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php', 'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php',
@ -256,7 +257,6 @@ phutil_register_library_map(array(
'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php', 'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php',
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php', 'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php', 'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php',
'ConpherenceCreateThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php',
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php',
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
@ -734,6 +734,7 @@ phutil_register_library_map(array(
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php', 'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php', 'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php', 'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php', 'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php', 'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
@ -1200,6 +1201,7 @@ phutil_register_library_map(array(
'HeraldTranscriptDestructionEngineExtension' => 'applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php', 'HeraldTranscriptDestructionEngineExtension' => 'applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php',
'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php', 'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php',
'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php', 'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php',
'HeraldTranscriptPHIDType' => 'applications/herald/phid/HeraldTranscriptPHIDType.php',
'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php', 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php',
'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php', 'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php',
'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php',
@ -2418,6 +2420,7 @@ phutil_register_library_map(array(
'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php', 'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php',
'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php', 'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php',
'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php', 'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php',
'PhabricatorLinkProfilePanel' => 'applications/search/profilepanel/PhabricatorLinkProfilePanel.php',
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php', 'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php', 'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php',
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php', 'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
@ -2825,6 +2828,16 @@ phutil_register_library_map(array(
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php', 'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php',
'PhabricatorProfilePanel' => 'applications/search/profilepanel/PhabricatorProfilePanel.php',
'PhabricatorProfilePanelConfiguration' => 'applications/search/storage/PhabricatorProfilePanelConfiguration.php',
'PhabricatorProfilePanelConfigurationQuery' => 'applications/search/query/PhabricatorProfilePanelConfigurationQuery.php',
'PhabricatorProfilePanelConfigurationTransaction' => 'applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php',
'PhabricatorProfilePanelEditEngine' => 'applications/search/editor/PhabricatorProfilePanelEditEngine.php',
'PhabricatorProfilePanelEditor' => 'applications/search/editor/PhabricatorProfilePanelEditor.php',
'PhabricatorProfilePanelEngine' => 'applications/search/engine/PhabricatorProfilePanelEngine.php',
'PhabricatorProfilePanelIconSet' => 'applications/search/profilepanel/PhabricatorProfilePanelIconSet.php',
'PhabricatorProfilePanelInterface' => 'applications/search/interface/PhabricatorProfilePanelInterface.php',
'PhabricatorProfilePanelPHIDType' => 'applications/search/phidtype/PhabricatorProfilePanelPHIDType.php',
'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php', 'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php', 'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php', 'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
@ -2855,6 +2868,7 @@ phutil_register_library_map(array(
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php', 'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php',
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
'PhabricatorProjectDetailsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php',
'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php',
'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php',
'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php',
@ -2876,6 +2890,7 @@ phutil_register_library_map(array(
'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php', 'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php',
'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php', 'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php',
'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php',
'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php', 'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php',
'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
@ -2885,6 +2900,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php', 'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php', 'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
@ -2908,6 +2924,7 @@ phutil_register_library_map(array(
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php', 'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php',
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php', 'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php',
@ -3009,6 +3026,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php', 'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php', 'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php', 'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php', 'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php', 'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
@ -4184,6 +4202,7 @@ phutil_register_library_map(array(
'ConduitStringParameterType' => 'ConduitParameterType', 'ConduitStringParameterType' => 'ConduitParameterType',
'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
'ConduitUserListParameterType' => 'ConduitListParameterType', 'ConduitUserListParameterType' => 'ConduitListParameterType',
'ConduitUserParameterType' => 'ConduitParameterType',
'ConduitWildParameterType' => 'ConduitListParameterType', 'ConduitWildParameterType' => 'ConduitListParameterType',
'ConpherenceColumnViewController' => 'ConpherenceController', 'ConpherenceColumnViewController' => 'ConpherenceController',
'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod', 'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod',
@ -4191,7 +4210,6 @@ phutil_register_library_map(array(
'ConpherenceConstants' => 'Phobject', 'ConpherenceConstants' => 'Phobject',
'ConpherenceController' => 'PhabricatorController', 'ConpherenceController' => 'PhabricatorController',
'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
'ConpherenceCreateThreadMailReceiver' => 'PhabricatorMailReceiver',
'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDAO' => 'PhabricatorLiskDAO',
'ConpherenceDurableColumnView' => 'AphrontTagView', 'ConpherenceDurableColumnView' => 'AphrontTagView',
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
@ -4707,6 +4725,7 @@ phutil_register_library_map(array(
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController', 'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryTag' => 'Phobject', 'DiffusionRepositoryTag' => 'Phobject',
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'DiffusionRequest' => 'Phobject', 'DiffusionRequest' => 'Phobject',
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionResolveUserQuery' => 'Phobject', 'DiffusionResolveUserQuery' => 'Phobject',
@ -5279,6 +5298,7 @@ phutil_register_library_map(array(
'HeraldTranscriptDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'HeraldTranscriptDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector', 'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector',
'HeraldTranscriptListController' => 'HeraldController', 'HeraldTranscriptListController' => 'HeraldController',
'HeraldTranscriptPHIDType' => 'PhabricatorPHIDType',
'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HeraldTranscriptTestCase' => 'PhabricatorTestCase', 'HeraldTranscriptTestCase' => 'PhabricatorTestCase',
@ -6697,6 +6717,7 @@ phutil_register_library_map(array(
'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType', 'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType',
'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase', 'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase',
'PhabricatorLinkProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorLipsumArtist' => 'Phobject', 'PhabricatorLipsumArtist' => 'Phobject',
'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow', 'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',
'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
@ -7168,6 +7189,20 @@ phutil_register_library_map(array(
), ),
'PhabricatorPolicyType' => 'PhabricatorPolicyConstants', 'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
'PhabricatorPonderApplication' => 'PhabricatorApplication', 'PhabricatorPonderApplication' => 'PhabricatorApplication',
'PhabricatorProfilePanel' => 'Phobject',
'PhabricatorProfilePanelConfiguration' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorProfilePanelConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProfilePanelConfigurationTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorProfilePanelEditEngine' => 'PhabricatorEditEngine',
'PhabricatorProfilePanelEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProfilePanelEngine' => 'Phobject',
'PhabricatorProfilePanelIconSet' => 'PhabricatorIconSet',
'PhabricatorProfilePanelPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProject' => array( 'PhabricatorProject' => array(
'PhabricatorProjectDAO', 'PhabricatorProjectDAO',
'PhabricatorApplicationTransactionInterface', 'PhabricatorApplicationTransactionInterface',
@ -7179,6 +7214,7 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface', 'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface', 'PhabricatorFulltextInterface',
'PhabricatorConduitResultInterface', 'PhabricatorConduitResultInterface',
'PhabricatorProfilePanelInterface',
), ),
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
'PhabricatorProjectApplication' => 'PhabricatorApplication', 'PhabricatorProjectApplication' => 'PhabricatorApplication',
@ -7220,6 +7256,7 @@ phutil_register_library_map(array(
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
'PhabricatorProjectDetailsProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectEditController' => 'PhabricatorProjectController',
'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine',
'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
@ -7240,6 +7277,7 @@ phutil_register_library_map(array(
'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController',
'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController', 'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController',
'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
@ -7249,6 +7287,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
@ -7275,6 +7314,7 @@ phutil_register_library_map(array(
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectViewController' => 'PhabricatorProjectController', 'PhabricatorProjectViewController' => 'PhabricatorProjectController',
'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
@ -7415,6 +7455,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorRepositoryType' => 'Phobject', 'PhabricatorRepositoryType' => 'Phobject',
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryURINormalizer' => 'Phobject', 'PhabricatorRepositoryURINormalizer' => 'Phobject',
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',

View file

@ -635,4 +635,19 @@ abstract class PhabricatorApplication
return $base.'(?:query/(?P<queryKey>[^/]+)/)?'; return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
} }
protected function getPanelRouting($controller) {
$edit_route = $this->getEditRoutePattern();
return array(
'(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller,
'(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller,
'(?P<panelAction>configure)/' => $controller,
'(?P<panelAction>reorder)/' => $controller,
'(?P<panelAction>edit)/'.$edit_route => $controller,
'(?P<panelAction>new)/(?<panelKey>[^/]+)/'.$edit_route => $controller,
'(?P<panelAction>builtin)/(?<panelID>[^/]+)/'.$edit_route
=> $controller,
);
}
} }

View file

@ -0,0 +1,47 @@
<?php
final class ConduitUserParameterType
extends ConduitParameterType {
protected function getParameterValue(array $request, $key) {
$value = parent::getParameterValue($request, $key);
if ($value === null) {
return null;
}
if (!is_string($value)) {
$this->raiseValidationException(
$request,
$key,
pht('Expected PHID or null, got something else.'));
}
$user_phids = id(new PhabricatorUserPHIDResolver())
->setViewer($this->getViewer())
->resolvePHIDs(array($value));
return nonempty(head($user_phids), null);
}
protected function getParameterTypeName() {
return 'phid|string|null';
}
protected function getParameterFormatDescriptions() {
return array(
pht('User PHID.'),
pht('Username.'),
pht('Literal null.'),
);
}
protected function getParameterExamples() {
return array(
'"PHID-USER-1111"',
'"alincoln"',
'null',
);
}
}

View file

@ -13,6 +13,8 @@ final class PhabricatorStorageSetupCheck extends PhabricatorSetupCheck {
$engines = PhabricatorFileStorageEngine::loadWritableChunkEngines(); $engines = PhabricatorFileStorageEngine::loadWritableChunkEngines();
$chunk_engine_active = (bool)$engines; $chunk_engine_active = (bool)$engines;
$this->checkS3();
if (!$chunk_engine_active) { if (!$chunk_engine_active) {
$doc_href = PhabricatorEnv::getDocLink('Configuring File Storage'); $doc_href = PhabricatorEnv::getDocLink('Configuring File Storage');
@ -140,4 +142,55 @@ final class PhabricatorStorageSetupCheck extends PhabricatorSetupCheck {
->addPhabricatorConfig('storage.local-disk.path'); ->addPhabricatorConfig('storage.local-disk.path');
} }
} }
private function checkS3() {
$access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key');
$secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key');
$region = PhabricatorEnv::getEnvConfig('amazon-s3.region');
$endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint');
$how_many = 0;
if (strlen($access_key)) {
$how_many++;
}
if (strlen($secret_key)) {
$how_many++;
}
if (strlen($region)) {
$how_many++;
}
if (strlen($endpoint)) {
$how_many++;
}
// Nothing configured, no issues here.
if ($how_many === 0) {
return;
}
// Everything configured, no issues here.
if ($how_many === 4) {
return;
}
$message = pht(
'File storage in Amazon S3 has been partially configured, but you are '.
'missing some required settings. S3 will not be available to store '.
'files until you complete the configuration. Either configure S3 fully '.
'or remove the partial configuration.');
$this->newIssue('storage.s3.partial-config')
->setShortName(pht('S3 Partially Configured'))
->setName(pht('Amazon S3 is Only Partially Configured'))
->setMessage($message)
->addPhabricatorConfig('amazon-s3.access-key')
->addPhabricatorConfig('amazon-s3.secret-key')
->addPhabricatorConfig('amazon-s3.region')
->addPhabricatorConfig('amazon-s3.endpoint');
}
} }

View file

@ -33,14 +33,27 @@ final class PhabricatorAWSConfigOptions
$this->newOption('amazon-s3.secret-key', 'string', null) $this->newOption('amazon-s3.secret-key', 'string', null)
->setHidden(true) ->setHidden(true)
->setDescription(pht('Secret key for Amazon S3.')), ->setDescription(pht('Secret key for Amazon S3.')),
$this->newOption('amazon-s3.region', 'string', null)
->setLocked(true)
->setDescription(
pht(
'Amazon S3 region where your S3 bucket is located. When you '.
'specify a region, you should also specify a corresponding '.
'endpoint with `amazon-s3.endpoint`. You can find a list of '.
'available regions and endpoints in the AWS documentation.'))
->addExample('us-west-1', pht('USWest Region')),
$this->newOption('amazon-s3.endpoint', 'string', null) $this->newOption('amazon-s3.endpoint', 'string', null)
->setLocked(true) ->setLocked(true)
->setDescription( ->setDescription(
pht( pht(
'Explicit S3 endpoint to use. Leave empty to have Phabricator '. 'Explicit S3 endpoint to use. This should be the endpoint '.
'select and endpoint. Normally, you do not need to set this.')) 'which corresponds to the region you have selected in '.
->addExample(null, pht('Use default endpoint')) '`amazon-s3.region`. Phabricator can not determine the correct '.
->addExample('s3.amazon.com', pht('Use specific endpoint')), 'endpoint automatically because some endpoint locations are '.
'irregular.'))
->addExample(
's3-us-west-1.amazonaws.com',
pht('Use specific endpoint')),
$this->newOption('amazon-ec2.access-key', 'string', null) $this->newOption('amazon-ec2.access-key', 'string', null)
->setLocked(true) ->setLocked(true)
->setDescription(pht('Access key for Amazon EC2.')), ->setDescription(pht('Access key for Amazon EC2.')),

View file

@ -21,7 +21,14 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject {
} }
if ($option->isCustomType()) { if ($option->isCustomType()) {
return $option->getCustomObject()->validateOption($option, $value); try {
return $option->getCustomObject()->validateOption($option, $value);
} catch (Exception $ex) {
// If custom validators threw exceptions, convert them to configuation
// validation exceptions so we repair the configuration and raise
// an error.
throw new PhabricatorConfigValidationException($ex->getMessage());
}
} }
switch ($option->getType()) { switch ($option->getType()) {

View file

@ -275,20 +275,21 @@ final class PhabricatorSetupIssueView extends AphrontView {
$update = array(); $update = array();
foreach ($configs as $config) { foreach ($configs as $config) {
if (idx($options, $config) && $options[$config]->getLocked()) { if (idx($options, $config) && $options[$config]->getLocked()) {
continue; $name = pht('View "%s"', $config);
} else {
$name = pht('Edit "%s"', $config);
} }
$link = phutil_tag( $link = phutil_tag(
'a', 'a',
array( array(
'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(), 'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(),
), ),
pht('Edit %s', $config)); $name);
$update[] = phutil_tag('li', array(), $link); $update[] = phutil_tag('li', array(), $link);
} }
if ($update) { if ($update) {
$update = phutil_tag('ul', array(), $update); $update = phutil_tag('ul', array(), $update);
if (!$related) { if (!$related) {
$update_info = phutil_tag( $update_info = phutil_tag(
'p', 'p',
array(), array(),

View file

@ -1,67 +0,0 @@
<?php
final class ConpherenceCreateThreadMailReceiver
extends PhabricatorMailReceiver {
public function isEnabled() {
$app_class = 'PhabricatorConpherenceApplication';
return PhabricatorApplication::isClassInstalled($app_class);
}
public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail) {
$usernames = $this->getMailUsernames($mail);
if (!$usernames) {
return false;
}
$users = $this->loadMailUsers($mail);
if (count($users) != count($usernames)) {
// At least some of the addresses are not users, so don't accept this as
// a new Conpherence thread.
return false;
}
return true;
}
private function getMailUsernames(PhabricatorMetaMTAReceivedMail $mail) {
$usernames = array();
foreach ($mail->getToAddresses() as $to_address) {
$address = self::stripMailboxPrefix($to_address);
$usernames[] = id(new PhutilEmailAddress($address))->getLocalPart();
}
return array_unique($usernames);
}
private function loadMailUsers(PhabricatorMetaMTAReceivedMail $mail) {
$usernames = $this->getMailUsernames($mail);
if (!$usernames) {
return array();
}
return id(new PhabricatorUser())->loadAllWhere(
'username in (%Ls)',
$usernames);
}
protected function processReceivedMail(
PhabricatorMetaMTAReceivedMail $mail,
PhabricatorUser $sender) {
$users = $this->loadMailUsers($mail);
$phids = mpull($users, 'getPHID');
$conpherence = id(new ConpherenceReplyHandler())
->setMailReceiver(ConpherenceThread::initializeNewRoom($sender))
->setMailAddedParticipantPHIDs($phids)
->setActor($sender)
->setExcludeMailRecipientPHIDs($mail->loadAllRecipientPHIDs())
->processEmail($mail);
if ($conpherence) {
$mail->setRelatedPHID($conpherence->getPHID());
}
}
}

View file

@ -71,9 +71,15 @@ final class PhabricatorDaemonBulkJobViewController
->setUser($viewer) ->setUser($viewer)
->setObject($job); ->setObject($job);
if ($job->isConfirming()) {
$continue_uri = $job->getMonitorURI();
} else {
$continue_uri = $job->getDoneURI();
}
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setHref($job->getDoneURI()) ->setHref($continue_uri)
->setIcon('fa-arrow-circle-o-right') ->setIcon('fa-arrow-circle-o-right')
->setName(pht('Continue'))); ->setName(pht('Continue')));

View file

@ -43,6 +43,26 @@ final class DifferentialCreateRawDiffConduitAPIMethod
$changes = $parser->parseDiff($raw_diff); $changes = $parser->parseDiff($raw_diff);
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes); $diff = DifferentialDiff::newFromRawChanges($viewer, $changes);
// We're bounded by doing INSERTs for all the hunks and changesets, so
// estimate the number of inserts we'll require.
$size = 0;
foreach ($diff->getChangesets() as $changeset) {
$hunks = $changeset->getHunks();
$size += 1 + count($hunks);
}
$raw_limit = 10000;
if ($size > $raw_limit) {
throw new Exception(
pht(
'The raw diff you have submitted is too large to parse (it affects '.
'more than %s paths and hunks). Differential should only be used '.
'for changes which are small enough to receive detailed human '.
'review. See "Differential User Guide: Large Changes" in the '.
'documentation for more information.',
new PhutilNumber($raw_limit)));
}
$diff_data_dict = array( $diff_data_dict = array(
'creationMethod' => 'web', 'creationMethod' => 'web',
'authorPHID' => $viewer->getPHID(), 'authorPHID' => $viewer->getPHID(),

View file

@ -129,8 +129,8 @@ final class DifferentialChangesetListView extends AphrontView {
array( array(
'pht' => array( 'pht' => array(
'Open in Editor' => pht('Open in Editor'), 'Open in Editor' => pht('Open in Editor'),
'Show Entire File' => pht('Show Entire File'), 'Show All Context' => pht('Show All Context'),
'Entire File Shown' => pht('Entire File Shown'), 'All Context Shown' => pht('All Context Shown'),
"Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"),
'Expand File' => pht('Expand File'), 'Expand File' => pht('Expand File'),
'Collapse File' => pht('Collapse File'), 'Collapse File' => pht('Collapse File'),

View file

@ -71,7 +71,7 @@ final class DiffusionLintSaveRunner extends Phobject {
} else if ($uuid) { } else if ($uuid) {
$repository_query->withUUIDs(array($uuid)); $repository_query->withUUIDs(array($uuid));
} else if ($remote_uri) { } else if ($remote_uri) {
$repository_query->withRemoteURIs(array($remote_uri)); $repository_query->withURIs(array($remote_uri));
} }
$repository = $repository_query->executeOne(); $repository = $repository_query->executeOne();

View file

@ -79,6 +79,7 @@ final class DiffusionQueryCommitsConduitAPIMethod
'repositoryPHID' => $commit->getRepository()->getPHID(), 'repositoryPHID' => $commit->getRepository()->getPHID(),
'identifier' => $commit->getCommitIdentifier(), 'identifier' => $commit->getCommitIdentifier(),
'epoch' => $commit->getEpoch(), 'epoch' => $commit->getEpoch(),
'authorEpoch' => $commit_data->getCommitDetail('authorEpoch'),
'uri' => $uri, 'uri' => $uri,
'isImporting' => !$commit->isImported(), 'isImporting' => !$commit->isImported(),
'summary' => $commit->getSummary(), 'summary' => $commit->getSummary(),
@ -99,6 +100,7 @@ final class DiffusionQueryCommitsConduitAPIMethod
->withIdentifier($commit->getCommitIdentifier()) ->withIdentifier($commit->getCommitIdentifier())
->execute(); ->execute();
$dict['authorEpoch'] = $lowlevel_commitref->getAuthorEpoch();
$dict['author'] = $lowlevel_commitref->getAuthor(); $dict['author'] = $lowlevel_commitref->getAuthor();
$dict['authorName'] = $lowlevel_commitref->getAuthorName(); $dict['authorName'] = $lowlevel_commitref->getAuthorName();
$dict['authorEmail'] = $lowlevel_commitref->getAuthorEmail(); $dict['authorEmail'] = $lowlevel_commitref->getAuthorEmail();

View file

@ -1761,7 +1761,7 @@ final class DiffusionBrowseController extends DiffusionController {
'size' => 600, 'size' => 600,
), ),
), ),
$commit->getShortName()); $commit->getLocalName());
$links[$identifier] = $commit_link; $links[$identifier] = $commit_link;
} }

View file

@ -492,8 +492,12 @@ final class DiffusionCommitController extends DiffusionController {
if (!$repository->isSVN()) { if (!$repository->isSVN()) {
$authored_info = id(new PHUIStatusItemView()); $authored_info = id(new PHUIStatusItemView());
// TODO: In Git, a distinct authorship date is available. When present,
// we should show it here. $author_epoch = $data->getCommitDetail('authorEpoch');
if ($author_epoch !== null) {
$authored_info->setNote(
phabricator_datetime($author_epoch, $viewer));
}
if ($author_phid) { if ($author_phid) {
$authored_info->setTarget($handles[$author_phid]->renderLink()); $authored_info->setTarget($handles[$author_phid]->renderLink());

View file

@ -17,21 +17,20 @@ final class DiffusionRepositoryEditBasicController
$v_name = $repository->getName(); $v_name = $repository->getName();
$v_desc = $repository->getDetail('description'); $v_desc = $repository->getDetail('description');
$v_clone_name = $repository->getDetail('clone-name'); $v_slug = $repository->getRepositorySlug();
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$repository->getPHID(), $repository->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$e_name = true; $e_name = true;
$e_slug = null;
$errors = array(); $errors = array();
$validation_exception = null;
if ($request->isFormPost()) { if ($request->isFormPost()) {
$v_name = $request->getStr('name'); $v_name = $request->getStr('name');
$v_desc = $request->getStr('description'); $v_desc = $request->getStr('description');
$v_projects = $request->getArr('projectPHIDs'); $v_projects = $request->getArr('projectPHIDs');
$v_slug = $request->getStr('slug');
if ($repository->isHosted()) {
$v_clone_name = $request->getStr('cloneName');
}
if (!strlen($v_name)) { if (!strlen($v_name)) {
$e_name = pht('Required'); $e_name = pht('Required');
@ -47,7 +46,7 @@ final class DiffusionRepositoryEditBasicController
$type_name = PhabricatorRepositoryTransaction::TYPE_NAME; $type_name = PhabricatorRepositoryTransaction::TYPE_NAME;
$type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; $type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
$type_edge = PhabricatorTransactions::TYPE_EDGE; $type_edge = PhabricatorTransactions::TYPE_EDGE;
$type_clone_name = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME; $type_slug = PhabricatorRepositoryTransaction::TYPE_SLUG;
$xactions[] = id(clone $template) $xactions[] = id(clone $template)
->setTransactionType($type_name) ->setTransactionType($type_name)
@ -58,8 +57,8 @@ final class DiffusionRepositoryEditBasicController
->setNewValue($v_desc); ->setNewValue($v_desc);
$xactions[] = id(clone $template) $xactions[] = id(clone $template)
->setTransactionType($type_clone_name) ->setTransactionType($type_slug)
->setNewValue($v_clone_name); ->setNewValue($v_slug);
$xactions[] = id(clone $template) $xactions[] = id(clone $template)
->setTransactionType($type_edge) ->setTransactionType($type_edge)
@ -71,13 +70,20 @@ final class DiffusionRepositoryEditBasicController
'=' => array_fuse($v_projects), '=' => array_fuse($v_projects),
)); ));
id(new PhabricatorRepositoryEditor()) $editor = id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true) ->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request) ->setContentSourceFromRequest($request)
->setActor($viewer) ->setActor($viewer);
->applyTransactions($repository, $xactions);
return id(new AphrontRedirectResponse())->setURI($edit_uri); try {
$editor->applyTransactions($repository, $xactions);
return id(new AphrontRedirectResponse())->setURI($edit_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_slug = $ex->getShortMessage($type_slug);
}
} }
} }
@ -93,22 +99,13 @@ final class DiffusionRepositoryEditBasicController
->setName('name') ->setName('name')
->setLabel(pht('Name')) ->setLabel(pht('Name'))
->setValue($v_name) ->setValue($v_name)
->setError($e_name)); ->setError($e_name))
->appendChild(
if ($repository->isHosted()) { id(new AphrontFormTextControl())
$form ->setName('slug')
->appendChild( ->setLabel(pht('Short Name'))
id(new AphrontFormTextControl()) ->setValue($v_slug)
->setName('cloneName') ->setError($e_slug))
->setLabel(pht('Clone/Checkout As'))
->setValue($v_clone_name)
->setCaption(
pht(
'Optional directory name to use when cloning or checking out '.
'this repository.')));
}
$form
->appendChild( ->appendChild(
id(new PhabricatorRemarkupControl()) id(new PhabricatorRemarkupControl())
->setUser($viewer) ->setUser($viewer)
@ -130,6 +127,7 @@ final class DiffusionRepositoryEditBasicController
$object_box = id(new PHUIObjectBoxView()) $object_box = id(new PHUIObjectBoxView())
->setHeaderText($title) ->setHeaderText($title)
->setValidationException($validation_exception)
->setForm($form) ->setForm($form)
->setFormErrors($errors); ->setFormErrors($errors);

View file

@ -286,15 +286,12 @@ final class DiffusionRepositoryEditMainController
$view->addProperty(pht('Type'), $type); $view->addProperty(pht('Type'), $type);
$view->addProperty(pht('Callsign'), $repository->getCallsign()); $view->addProperty(pht('Callsign'), $repository->getCallsign());
$clone_name = $repository->getDetail('clone-name'); $short_name = $repository->getRepositorySlug();
if ($short_name === null) {
if ($repository->isHosted()) { $short_name = $repository->getCloneName();
$view->addProperty( $short_name = phutil_tag('em', array(), $short_name);
pht('Clone/Checkout As'),
$clone_name
? $clone_name.'/'
: phutil_tag('em', array(), $repository->getCloneName().'/'));
} }
$view->addProperty(pht('Short Name'), $short_name);
$view->invokeWillRenderEvent(); $view->invokeWillRenderEvent();

View file

@ -3,6 +3,7 @@
final class DiffusionCommitRef extends Phobject { final class DiffusionCommitRef extends Phobject {
private $message; private $message;
private $authorEpoch;
private $authorName; private $authorName;
private $authorEmail; private $authorEmail;
private $committerName; private $committerName;
@ -11,6 +12,7 @@ final class DiffusionCommitRef extends Phobject {
public static function newFromConduitResult(array $result) { public static function newFromConduitResult(array $result) {
$ref = id(new DiffusionCommitRef()) $ref = id(new DiffusionCommitRef())
->setAuthorEpoch(idx($result, 'authorEpoch'))
->setCommitterEmail(idx($result, 'committerEmail')) ->setCommitterEmail(idx($result, 'committerEmail'))
->setCommitterName(idx($result, 'committerName')) ->setCommitterName(idx($result, 'committerName'))
->setAuthorEmail(idx($result, 'authorEmail')) ->setAuthorEmail(idx($result, 'authorEmail'))
@ -38,6 +40,15 @@ final class DiffusionCommitRef extends Phobject {
return $this->hashes; return $this->hashes;
} }
public function setAuthorEpoch($author_epoch) {
$this->authorEpoch = $author_epoch;
return $this;
}
public function getAuthorEpoch() {
return $this->authorEpoch;
}
public function setCommitterEmail($committer_email) { public function setCommitterEmail($committer_email) {
$this->committerEmail = $committer_email; $this->committerEmail = $committer_email;
return $this; return $this;

View file

@ -308,6 +308,7 @@ final class DiffusionCommitHookEngine extends Phobject {
$rules = null; $rules = null;
$blocking_effect = null; $blocking_effect = null;
$blocked_update = null; $blocked_update = null;
$blocking_xscript = null;
foreach ($updates as $update) { foreach ($updates as $update) {
$adapter = id(clone $adapter_template) $adapter = id(clone $adapter_template)
->setPushLog($update); ->setPushLog($update);
@ -332,6 +333,7 @@ final class DiffusionCommitHookEngine extends Phobject {
if ($effect->getAction() == $block_action) { if ($effect->getAction() == $block_action) {
$blocking_effect = $effect; $blocking_effect = $effect;
$blocked_update = $update; $blocked_update = $update;
$blocking_xscript = $xscript;
break; break;
} }
} }
@ -357,13 +359,16 @@ final class DiffusionCommitHookEngine extends Phobject {
throw new DiffusionCommitHookRejectException( throw new DiffusionCommitHookRejectException(
pht( pht(
"This push was rejected by Herald push rule %s.\n". "This push was rejected by Herald push rule %s.\n".
"Change: %s\n". " Change: %s\n".
" Rule: %s\n". " Rule: %s\n".
"Reason: %s", " Reason: %s\n".
"Transcript: %s",
$rule->getMonogram(), $rule->getMonogram(),
$blocked_name, $blocked_name,
$rule->getName(), $rule->getName(),
$message)); $message,
PhabricatorEnv::getProductionURI(
'/herald/transcript/'.$blocking_xscript->getID().'/')));
} }
} }

View file

@ -0,0 +1,22 @@
<?php
final class DiffusionRepositoryURIsIndexEngineExtension
extends PhabricatorIndexEngineExtension {
const EXTENSIONKEY = 'diffusion.repositories.uri';
public function getExtensionName() {
return pht('Repository URIs');
}
public function shouldIndexObject($object) {
return ($object instanceof PhabricatorRepository);
}
public function indexObject(
PhabricatorIndexEngine $engine,
$object) {
$object->updateURIIndex();
}
}

View file

@ -52,7 +52,7 @@ final class DiffusionLowLevelCommitQuery
'UTF-8', 'UTF-8',
implode( implode(
'%x00', '%x00',
array('%e', '%cn', '%ce', '%an', '%ae', '%T', '%s%n%n%b')), array('%e', '%cn', '%ce', '%an', '%ae', '%T', '%at', '%s%n%n%b')),
$this->identifier); $this->identifier);
$parts = explode("\0", $info); $parts = explode("\0", $info);
@ -77,13 +77,19 @@ final class DiffusionLowLevelCommitQuery
->setHashValue($parts[4]), ->setHashValue($parts[4]),
); );
$author_epoch = (int)$parts[5];
if (!$author_epoch) {
$author_epoch = null;
}
return id(new DiffusionCommitRef()) return id(new DiffusionCommitRef())
->setCommitterName($parts[0]) ->setCommitterName($parts[0])
->setCommitterEmail($parts[1]) ->setCommitterEmail($parts[1])
->setAuthorName($parts[2]) ->setAuthorName($parts[2])
->setAuthorEmail($parts[3]) ->setAuthorEmail($parts[3])
->setHashes($hashes) ->setHashes($hashes)
->setMessage($parts[5]); ->setAuthorEpoch($author_epoch)
->setMessage($parts[6]);
} }
private function loadMercurialCommitRef() { private function loadMercurialCommitRef() {

View file

@ -24,13 +24,31 @@ final class DiffusionRepositoryDatasource
->withDatasourceQuery($raw_query); ->withDatasourceQuery($raw_query);
$repos = $this->executeQuery($query); $repos = $this->executeQuery($query);
$type_icon = id(new PhabricatorRepositoryRepositoryPHIDType())
->getTypeIcon();
$image_sprite =
"phabricator-search-icon phui-font-fa phui-icon-view {$type_icon}";
$results = array(); $results = array();
foreach ($repos as $repo) { foreach ($repos as $repo) {
$display_name = $repo->getMonogram().' '.$repo->getName();
$name = $display_name;
$slug = $repo->getRepositorySlug();
if (strlen($slug)) {
$name = "{$name} {$slug}";
}
$results[] = id(new PhabricatorTypeaheadResult()) $results[] = id(new PhabricatorTypeaheadResult())
->setName($repo->getMonogram().' '.$repo->getName()) ->setName($name)
->setDisplayName($display_name)
->setURI($repo->getURI()) ->setURI($repo->getURI())
->setPHID($repo->getPHID()) ->setPHID($repo->getPHID())
->setPriorityString($repo->getMonogram()); ->setPriorityString($repo->getMonogram())
->setPriorityType('repo')
->setImageSprite($image_sprite)
->setDisplayType(pht('Repository'));
} }
return $results; return $results;

View file

@ -128,7 +128,7 @@ abstract class DiffusionView extends AphrontView {
$commit, $commit,
$summary = '') { $summary = '') {
$commit_name = $repository->formatCommitName($commit); $commit_name = $repository->formatCommitName($commit, $local = true);
if (strlen($summary)) { if (strlen($summary)) {
$commit_name .= ': '.$summary; $commit_name .= ': '.$summary;

View file

@ -35,7 +35,7 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication {
return array( return array(
'/diviner/' => array( '/diviner/' => array(
'' => 'DivinerMainController', '' => 'DivinerMainController',
'query/((?<key>[^/]+)/)?' => 'DivinerAtomListController', 'query/((?<queryKey>[^/]+)/)?' => 'DivinerAtomListController',
'find/' => 'DivinerFindController', 'find/' => 'DivinerFindController',
), ),
'/book/(?P<book>[^/]+)/' => 'DivinerBookController', '/book/(?P<book>[^/]+)/' => 'DivinerBookController',

View file

@ -7,14 +7,9 @@ final class DivinerAtomListController extends DivinerController {
} }
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$query_key = $request->getURIData('key'); return id(new DivinerAtomSearchEngine())
->setController($this)
$controller = id(new PhabricatorApplicationSearchController()) ->buildResponse();
->setQueryKey($query_key)
->setSearchEngine(new DivinerAtomSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
} }
} }

View file

@ -2,19 +2,9 @@
abstract class DivinerController extends PhabricatorController { abstract class DivinerController extends PhabricatorController {
protected function buildSideNavView() {
$menu = $this->buildApplicationMenu();
return AphrontSideNavFilterView::newFromMenu($menu);
}
public function buildApplicationMenu() { public function buildApplicationMenu() {
$menu = new PHUIListView(); return $this->newApplicationMenu()
->setSearchEngine(new DivinerAtomSearchEngine());
id(new DivinerAtomSearchEngine())
->setViewer($this->getRequest()->getViewer())
->addNavigationItems($menu);
return $menu;
} }
protected function renderAtomList(array $symbols) { protected function renderAtomList(array $symbols) {

View file

@ -103,7 +103,7 @@ final class DivinerAtomSearchEngine extends PhabricatorApplicationSearchEngine {
protected function getBuiltinQueryNames() { protected function getBuiltinQueryNames() {
return array( return array(
'all' => pht('All'), 'all' => pht('All Atoms'),
); );
} }

View file

@ -70,7 +70,7 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
return array( return array(
'/F(?P<id>[1-9]\d*)' => 'PhabricatorFileInfoController', '/F(?P<id>[1-9]\d*)' => 'PhabricatorFileInfoController',
'/file/' => array( '/file/' => array(
'(query/(?P<key>[^/]+)/)?' => 'PhabricatorFileListController', '(query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFileListController',
'upload/' => 'PhabricatorFileUploadController', 'upload/' => 'PhabricatorFileUploadController',
'dropupload/' => 'PhabricatorFileDropUploadController', 'dropupload/' => 'PhabricatorFileDropUploadController',
'compose/' => 'PhabricatorFileComposeController', 'compose/' => 'PhabricatorFileComposeController',

View file

@ -2,28 +2,9 @@
abstract class PhabricatorFileController extends PhabricatorController { abstract class PhabricatorFileController extends PhabricatorController {
protected function buildSideNavView() {
$menu = $this->buildMenu($for_devices = false);
return AphrontSideNavFilterView::newFromMenu($menu);
}
public function buildApplicationMenu() { public function buildApplicationMenu() {
return $this->buildMenu($for_devices = true); return $this->newApplicationMenu()
->setSearchEngine(new PhabricatorFileSearchEngine());
} }
private function buildMenu($for_devices) {
$menu = new PHUIListView();
if ($for_devices) {
$menu->newLink(pht('Upload File'), $this->getApplicationURI('/upload/'));
}
id(new PhabricatorFileSearchEngine())
->setViewer($this->getRequest()->getUser())
->addNavigationItems($menu);
return $menu;
}
} }

View file

@ -11,16 +11,14 @@ final class PhabricatorFileListController extends PhabricatorFileController {
} }
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController()) return id(new PhabricatorFileSearchEngine())
->setQueryKey($request->getURIData('key')) ->setController($this)
->setSearchEngine(new PhabricatorFileSearchEngine()) ->buildResponse();
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
} }
protected function buildApplicationCrumbs() { protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs(); $crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction( $crumbs->addAction(
id(new PHUIListItemView()) id(new PHUIListItemView())
->setName(pht('Upload File')) ->setName(pht('Upload File'))

View file

@ -28,8 +28,14 @@ final class PhabricatorS3FileStorageEngine
$bucket = PhabricatorEnv::getEnvConfig('storage.s3.bucket'); $bucket = PhabricatorEnv::getEnvConfig('storage.s3.bucket');
$access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key'); $access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key');
$secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key'); $secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key');
$endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint');
$region = PhabricatorEnv::getEnvConfig('amazon-s3.region');
return (strlen($bucket) && strlen($access_key) && strlen($secret_key)); return (strlen($bucket) &&
strlen($access_key) &&
strlen($secret_key) &&
strlen($endpoint) &&
strlen($region));
} }
@ -68,11 +74,11 @@ final class PhabricatorS3FileStorageEngine
'type' => 's3', 'type' => 's3',
'method' => 'putObject', 'method' => 'putObject',
)); ));
$s3->putObject(
$data, $s3
$this->getBucketName(), ->setParametersForPutObject($name, $data)
$name, ->resolve();
$acl = 'private');
$profiler->endServiceCall($call_id, array()); $profiler->endServiceCall($call_id, array());
return $name; return $name;
@ -84,24 +90,21 @@ final class PhabricatorS3FileStorageEngine
*/ */
public function readFile($handle) { public function readFile($handle) {
$s3 = $this->newS3API(); $s3 = $this->newS3API();
$profiler = PhutilServiceProfiler::getInstance(); $profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall( $call_id = $profiler->beginServiceCall(
array( array(
'type' => 's3', 'type' => 's3',
'method' => 'getObject', 'method' => 'getObject',
)); ));
$result = $s3->getObject(
$this->getBucketName(), $result = $s3
$handle); ->setParametersForGetObject($handle)
->resolve();
$profiler->endServiceCall($call_id, array()); $profiler->endServiceCall($call_id, array());
// NOTE: The implementation of the API that we're using may respond with return $result;
// a successful result that has length 0 and no body property.
if (isset($result->body)) {
return $result->body;
} else {
return '';
}
} }
@ -109,17 +112,20 @@ final class PhabricatorS3FileStorageEngine
* Delete a blob from Amazon S3. * Delete a blob from Amazon S3.
*/ */
public function deleteFile($handle) { public function deleteFile($handle) {
AphrontWriteGuard::willWrite();
$s3 = $this->newS3API(); $s3 = $this->newS3API();
AphrontWriteGuard::willWrite();
$profiler = PhutilServiceProfiler::getInstance(); $profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall( $call_id = $profiler->beginServiceCall(
array( array(
'type' => 's3', 'type' => 's3',
'method' => 'deleteObject', 'method' => 'deleteObject',
)); ));
$s3->deleteObject(
$this->getBucketName(), $s3
$handle); ->setParametersForDeleteObject($handle)
->resolve();
$profiler->endServiceCall($call_id, array()); $profiler->endServiceCall($call_id, array());
} }
@ -147,33 +153,19 @@ final class PhabricatorS3FileStorageEngine
* Create a new S3 API object. * Create a new S3 API object.
* *
* @task internal * @task internal
* @phutil-external-symbol class S3
*/ */
private function newS3API() { private function newS3API() {
$libroot = dirname(phutil_get_library_root('phabricator'));
require_once $libroot.'/externals/s3/S3.php';
$access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key'); $access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key');
$secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key'); $secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key');
$region = PhabricatorEnv::getEnvConfig('amazon-s3.region');
$endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint'); $endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint');
if (!$access_key || !$secret_key) { return id(new PhutilAWSS3Future())
throw new PhabricatorFileStorageConfigurationException( ->setAccessKey($access_key)
pht( ->setSecretKey(new PhutilOpaqueEnvelope($secret_key))
"Specify '%s' and '%s'!", ->setRegion($region)
'amazon-s3.access-key', ->setEndpoint($endpoint)
'amazon-s3.secret-key')); ->setBucket($this->getBucketName());
}
if ($endpoint !== null) {
$s3 = new S3($access_key, $secret_key, $use_ssl = true, $endpoint);
} else {
$s3 = new S3($access_key, $secret_key, $use_ssl = true);
}
$s3->setExceptions(true);
return $s3;
} }
} }

View file

@ -4,26 +4,29 @@ final class PhabricatorFileUploadException extends Exception {
public function __construct($code) { public function __construct($code) {
$map = array( $map = array(
'UPLOAD_ERR_INI_SIZE' => pht( UPLOAD_ERR_INI_SIZE => pht(
"Uploaded file is too large: current limit is %s. To adjust ". "Uploaded file is too large: current limit is %s. To adjust ".
"this limit change '%s' in php.ini.", "this limit change '%s' in php.ini.",
ini_get('upload_max_filesize'), ini_get('upload_max_filesize'),
'upload_max_filesize'), 'upload_max_filesize'),
'UPLOAD_ERR_FORM_SIZE' => pht( UPLOAD_ERR_FORM_SIZE => pht(
'File is too large.'), 'File is too large.'),
'UPLOAD_ERR_PARTIAL' => pht( UPLOAD_ERR_PARTIAL => pht(
'File was only partially transferred, upload did not complete.'), 'File was only partially transferred, upload did not complete.'),
'UPLOAD_ERR_NO_FILE' => pht( UPLOAD_ERR_NO_FILE => pht(
'No file was uploaded.'), 'No file was uploaded.'),
'UPLOAD_ERR_NO_TMP_DIR' => pht( UPLOAD_ERR_NO_TMP_DIR => pht(
'Unable to write file: temporary directory does not exist.'), 'Unable to write file: temporary directory does not exist.'),
'UPLOAD_ERR_CANT_WRITE' => pht( UPLOAD_ERR_CANT_WRITE => pht(
'Unable to write file: failed to write to temporary directory.'), 'Unable to write file: failed to write to temporary directory.'),
'UPLOAD_ERR_EXTENSION' => pht( UPLOAD_ERR_EXTENSION => pht(
'Unable to upload: a PHP extension stopped the upload.'), 'Unable to upload: a PHP extension stopped the upload.'),
); );
$message = idx($map, $code, pht('Upload failed: unknown error.')); $message = idx(
$map,
$code,
pht('Upload failed: unknown error (%s).', $code));
parent::__construct($message, $code); parent::__construct($message, $code);
} }
} }

View file

@ -18,6 +18,20 @@ final class PhabricatorFilesManagementMigrateWorkflow
'name' => 'dry-run', 'name' => 'dry-run',
'help' => pht('Show what would be migrated.'), 'help' => pht('Show what would be migrated.'),
), ),
array(
'name' => 'min-size',
'param' => 'bytes',
'help' => pht(
'Do not migrate data for files which are smaller than a given '.
'filesize.'),
),
array(
'name' => 'max-size',
'param' => 'bytes',
'help' => pht(
'Do not migrate data for files which are larger than a given '.
'filesize.'),
),
array( array(
'name' => 'all', 'name' => 'all',
'help' => pht('Migrate all files.'), 'help' => pht('Migrate all files.'),
@ -30,10 +44,8 @@ final class PhabricatorFilesManagementMigrateWorkflow
} }
public function execute(PhutilArgumentParser $args) { public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole(); $target_key = $args->getArg('engine');
if (!$target_key) {
$engine_id = $args->getArg('engine');
if (!$engine_id) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( pht(
'Specify an engine to migrate to with `%s`. '. 'Specify an engine to migrate to with `%s`. '.
@ -42,7 +54,7 @@ final class PhabricatorFilesManagementMigrateWorkflow
'files engines')); 'files engines'));
} }
$engine = PhabricatorFile::buildEngine($engine_id); $target_engine = PhabricatorFile::buildEngine($target_key);
$iterator = $this->buildIterator($args); $iterator = $this->buildIterator($args);
if (!$iterator) { if (!$iterator) {
@ -55,63 +67,147 @@ final class PhabricatorFilesManagementMigrateWorkflow
$is_dry_run = $args->getArg('dry-run'); $is_dry_run = $args->getArg('dry-run');
$min_size = (int)$args->getArg('min-size');
$max_size = (int)$args->getArg('max-size');
$failed = array(); $failed = array();
$engines = PhabricatorFileStorageEngine::loadAllEngines();
$total_bytes = 0;
$total_files = 0;
foreach ($iterator as $file) { foreach ($iterator as $file) {
$fid = 'F'.$file->getID(); $monogram = $file->getMonogram();
if ($file->getStorageEngine() == $engine_id) { $engine_key = $file->getStorageEngine();
$console->writeOut( $engine = idx($engines, $engine_key);
if (!$engine) {
echo tsprintf(
"%s\n", "%s\n",
pht( pht(
"%s: Already stored on '%s'", '%s: Uses unknown storage engine "%s".',
$fid, $monogram,
$engine_id)); $engine_key));
$failed[] = $file;
continue;
}
if ($engine->isChunkEngine()) {
echo tsprintf(
"%s\n",
pht(
'%s: Stored as chunks, no data to migrate directly.',
$monogram));
continue;
}
if ($engine_key === $target_key) {
echo tsprintf(
"%s\n",
pht(
'%s: Already stored in engine "%s".',
$monogram,
$target_key));
continue;
}
$byte_size = $file->getByteSize();
if ($min_size && ($byte_size < $min_size)) {
echo tsprintf(
"%s\n",
pht(
'%s: File size (%s) is smaller than minimum size (%s).',
$monogram,
phutil_format_bytes($byte_size),
phutil_format_bytes($min_size)));
continue;
}
if ($max_size && ($byte_size > $max_size)) {
echo tsprintf(
"%s\n",
pht(
'%s: File size (%s) is larger than maximum size (%s).',
$monogram,
phutil_format_bytes($byte_size),
phutil_format_bytes($max_size)));
continue; continue;
} }
if ($is_dry_run) { if ($is_dry_run) {
$console->writeOut( echo tsprintf(
"%s\n", "%s\n",
pht( pht(
"%s: Would migrate from '%s' to '%s' (dry run)", '%s: (%s) Would migrate from "%s" to "%s" (dry run)...',
$fid, $monogram,
$file->getStorageEngine(), phutil_format_bytes($byte_size),
$engine_id)); $engine_key,
continue; $target_key));
} else {
echo tsprintf(
"%s\n",
pht(
'%s: (%s) Migrating from "%s" to "%s"...',
$monogram,
phutil_format_bytes($byte_size),
$engine_key,
$target_key));
} }
$console->writeOut(
"%s\n",
pht(
"%s: Migrating from '%s' to '%s'...",
$fid,
$file->getStorageEngine(),
$engine_id));
try { try {
$file->migrateToEngine($engine); if ($is_dry_run) {
$console->writeOut("%s\n", pht('Done.')); // Do nothing, this is a dry run.
} else {
$file->migrateToEngine($target_engine);
}
$total_files += 1;
$total_bytes += $byte_size;
echo tsprintf(
"%s\n",
pht('Done.'));
} catch (Exception $ex) { } catch (Exception $ex) {
$console->writeOut("%s\n", pht('Failed!')); echo tsprintf(
$console->writeErr("%s\n", (string)$ex); "%s\n",
pht('Failed! %s', (string)$ex));
$failed[] = $file; $failed[] = $file;
throw $ex;
} }
} }
echo tsprintf(
"%s\n",
pht(
'Total Migrated Files: %s',
new PhutilNumber($total_files)));
echo tsprintf(
"%s\n",
pht(
'Total Migrated Bytes: %s',
phutil_format_bytes($total_bytes)));
if ($is_dry_run) {
echo tsprintf(
"%s\n",
pht(
'This was a dry run, so no real migrations were performed.'));
}
if ($failed) { if ($failed) {
$console->writeOut("**%s**\n", pht('Failures!')); $monograms = mpull($failed, 'getMonogram');
$ids = array();
foreach ($failed as $file) { echo tsprintf(
$ids[] = 'F'.$file->getID(); "%s\n",
} pht('Failures: %s.', implode(', ', $monograms)));
$console->writeOut("%s\n", implode(', ', $ids));
return 1; return 1;
} else {
$console->writeOut("**%s**\n", pht('Success!'));
return 0;
} }
return 0;
} }
} }

View file

@ -79,7 +79,7 @@ final class PhabricatorEmbedFileRemarkupRule
require_celerity_resource('lightbox-attachment-css'); require_celerity_resource('lightbox-attachment-css');
$attrs = array(); $attrs = array();
$image_class = null; $image_class = 'phabricator-remarkup-embed-image';
$use_size = true; $use_size = true;
if (!$options['size']) { if (!$options['size']) {
@ -117,8 +117,6 @@ final class PhabricatorEmbedFileRemarkupRule
$attrs['width'] = $x; $attrs['width'] = $x;
$attrs['height'] = $y; $attrs['height'] = $y;
} }
$image_class = 'phabricator-remarkup-embed-image';
break; break;
} }
} }

View file

@ -0,0 +1,42 @@
<?php
final class HeraldTranscriptPHIDType extends PhabricatorPHIDType {
const TYPECONST = 'HLXS';
public function getTypeName() {
return pht('Herald Transcript');
}
public function newObject() {
return new HeraldTranscript();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorHeraldApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new HeraldTranscriptQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$xscript = $objects[$phid];
$id = $xscript->getID();
$handle->setName(pht('Transcript %s', $id));
$handle->setURI("/herald/transcript/${id}/");
}
}
}

View file

@ -4,6 +4,7 @@ final class HeraldTranscriptQuery
extends PhabricatorCursorPagedPolicyAwareQuery { extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids; private $ids;
private $phids;
private $objectPHIDs; private $objectPHIDs;
private $needPartialRecords; private $needPartialRecords;
@ -12,6 +13,11 @@ final class HeraldTranscriptQuery
return $this; return $this;
} }
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectPHIDs(array $phids) { public function withObjectPHIDs(array $phids) {
$this->objectPHIDs = $phids; $this->objectPHIDs = $phids;
return $this; return $this;
@ -95,6 +101,13 @@ final class HeraldTranscriptQuery
$this->ids); $this->ids);
} }
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectPHIDs) { if ($this->objectPHIDs) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn_r,

View file

@ -190,7 +190,8 @@ final class HeraldTranscript extends HeraldDAO
} }
public function generatePHID() { public function generatePHID() {
return PhabricatorPHID::generateNewPHID('HLXS'); return PhabricatorPHID::generateNewPHID(
HeraldTranscriptPHIDType::TYPECONST);
} }
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -116,7 +116,15 @@ final class ManiphestTaskPriority extends ManiphestConstants {
return $config; return $config;
} }
public static function validateConfiguration(array $config) { public static function validateConfiguration($config) {
if (!is_array($config)) {
throw new Exception(
pht(
'Configuration is not valid. Maniphest priority configurations '.
'must be dictionaries.',
$config));
}
foreach ($config as $key => $value) { foreach ($config as $key => $value) {
if (!ctype_digit((string)$key)) { if (!ctype_digit((string)$key)) {
throw new Exception( throw new Exception(

View file

@ -829,6 +829,33 @@ final class ManiphestTransactionEditor
last($with_effect)); last($with_effect));
} }
break; break;
case ManiphestTransaction::TYPE_OWNER:
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!strlen($new)) {
continue;
}
if ($new === $old) {
continue;
}
$assignee_list = id(new PhabricatorPeopleQuery())
->setViewer($this->getActor())
->withPHIDs(array($new))
->execute();
if (!$assignee_list) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'User "%s" is not a valid user.',
$new),
$xaction);
}
}
break;
} }
return $errors; return $errors;

View file

@ -327,9 +327,18 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
// really be all the headers. It would be nice to pass the raw headers // really be all the headers. It would be nice to pass the raw headers
// through from the upper layers where possible. // through from the upper layers where possible.
// On the MimeMailParser pathway, we arrive here with a list value for
// headers that appeared multiple times in the original mail. Be
// accommodating until header handling gets straightened out.
$headers = array(); $headers = array();
foreach ($this->headers as $key => $value) { foreach ($this->headers as $key => $values) {
$headers[] = pht('%s: %s', $key, $value); if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $value) {
$headers[] = pht('%s: %s', $key, $value);
}
} }
$headers = implode("\n", $headers); $headers = implode("\n", $headers);

View file

@ -285,6 +285,76 @@ final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
pht('Extended Policy with Cycle')); pht('Extended Policy with Cycle'));
} }
/**
* Test bulk checks of extended policies.
*
* This is testing an issue with extended policy filtering which allowed
* unusual inputs to slip objects through the filter. See D14993.
*/
public function testBulkExtendedPolicies() {
$object1 = $this->buildObject(PhabricatorPolicies::POLICY_USER)
->setPHID('PHID-TEST-1');
$object2 = $this->buildObject(PhabricatorPolicies::POLICY_USER)
->setPHID('PHID-TEST-2');
$object3 = $this->buildObject(PhabricatorPolicies::POLICY_USER)
->setPHID('PHID-TEST-3');
$extended = $this->buildObject(PhabricatorPolicies::POLICY_ADMIN)
->setPHID('PHID-TEST-999');
$object1->setExtendedPolicies(
array(
PhabricatorPolicyCapability::CAN_VIEW => array(
array(
$extended,
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
),
),
),
));
$object2->setExtendedPolicies(
array(
PhabricatorPolicyCapability::CAN_VIEW => array(
array($extended, PhabricatorPolicyCapability::CAN_VIEW),
),
));
$object3->setExtendedPolicies(
array(
PhabricatorPolicyCapability::CAN_VIEW => array(
array(
$extended,
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
),
),
),
));
$user = $this->buildUser('user');
$visible = id(new PhabricatorPolicyFilter())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
))
->apply(
array(
$object1,
$object2,
$object3,
));
$this->assertEqual(array(), $visible);
}
/** /**
* An omnipotent user should be able to see even objects with invalid * An omnipotent user should be able to see even objects with invalid
* policies. * policies.
@ -430,10 +500,12 @@ final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
$object->setCapabilities( $object->setCapabilities(
array( array(
PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
)); ));
$object->setPolicies( $object->setPolicies(
array( array(
PhabricatorPolicyCapability::CAN_VIEW => $policy, PhabricatorPolicyCapability::CAN_VIEW => $policy,
PhabricatorPolicyCapability::CAN_EDIT => $policy,
)); ));
return $object; return $object;

View file

@ -321,7 +321,7 @@ final class PhabricatorPolicyFilter extends Phobject {
$objects_in = array(); $objects_in = array();
foreach ($structs as $struct) { foreach ($structs as $struct) {
$extended_key = $struct['key']; $extended_key = $struct['key'];
if (empty($extended_objects[$key])) { if (empty($extended_objects[$extended_key])) {
// If this object has already been rejected by an earlier filtering // If this object has already been rejected by an earlier filtering
// pass, we don't need to do any tests on it. // pass, we don't need to do any tests on it.
continue; continue;
@ -335,8 +335,8 @@ final class PhabricatorPolicyFilter extends Phobject {
// We weren't able to load the corresponding object, so just // We weren't able to load the corresponding object, so just
// reject this result outright. // reject this result outright.
$reject = $extended_objects[$key]; $reject = $extended_objects[$extended_key];
unset($extended_objects[$key]); unset($extended_objects[$extended_key]);
// TODO: This could be friendlier. // TODO: This could be friendlier.
$this->rejectObject($reject, false, '<bad-ref>'); $this->rejectObject($reject, false, '<bad-ref>');

View file

@ -61,6 +61,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
=> 'PhabricatorProjectEditPictureController', => 'PhabricatorProjectEditPictureController',
$this->getEditRoutePattern('edit/') $this->getEditRoutePattern('edit/')
=> 'PhabricatorProjectEditController', => 'PhabricatorProjectEditController',
'(?P<projectID>[1-9]\d*)/panel/'
=> $this->getPanelRouting('PhabricatorProjectPanelController'),
'subprojects/(?P<id>[1-9]\d*)/' 'subprojects/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectSubprojectsController', => 'PhabricatorProjectSubprojectsController',
'milestones/(?P<id>[1-9]\d*)/' 'milestones/(?P<id>[1-9]\d*)/'

View file

@ -3,11 +3,12 @@
abstract class PhabricatorProjectBoardController abstract class PhabricatorProjectBoardController
extends PhabricatorProjectController { extends PhabricatorProjectController {
public function buildIconNavView(PhabricatorProject $project) { protected function getProfileMenu() {
$id = $project->getID(); $menu = parent::getProfileMenu();
$nav = parent::buildIconNavView($project);
$nav->selectFilter("board/{$id}/"); $menu->selectFilter(PhabricatorProject::PANEL_WORKBOARD);
$nav->addClass('project-board-nav'); $menu->addClass('project-board-nav');
return $nav;
return $menu;
} }
} }

View file

@ -384,7 +384,7 @@ final class PhabricatorProjectBoardViewController
->appendChild($board) ->appendChild($board)
->addClass('project-board-wrapper'); ->addClass('project-board-wrapper');
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
return $this->newPage() return $this->newPage()
->setTitle(pht('%s Board', $project->getName())) ->setTitle(pht('%s Board', $project->getName()))

View file

@ -49,15 +49,16 @@ final class PhabricatorProjectColumnDetailController
->setHeader($header) ->setHeader($header)
->addPropertyList($properties); ->addPropertyList($properties);
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->appendChild($box);
$nav->appendChild($timeline);
return $this->buildApplicationPage( return $this->newPage()
$nav, ->setTitle($title)
array( ->setNavigation($nav)
'title' => $title, ->appendChild(
)); array(
$box,
$timeline,
));
} }
private function buildHeaderView(PhabricatorProjectColumn $column) { private function buildHeaderView(PhabricatorProjectColumn $column) {

View file

@ -144,13 +144,11 @@ final class PhabricatorProjectColumnEditController
->setValidationException($validation_exception) ->setValidationException($validation_exception)
->setForm($form); ->setForm($form);
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->appendChild($form_box);
return $this->buildApplicationPage( return $this->newPage()
$nav, ->setTitle($title)
array( ->setNavigation($nav)
'title' => $title, ->appendChild($form_box);
));
} }
} }

View file

@ -3,6 +3,7 @@
abstract class PhabricatorProjectController extends PhabricatorController { abstract class PhabricatorProjectController extends PhabricatorController {
private $project; private $project;
private $profileMenu;
protected function setProject(PhabricatorProject $project) { protected function setProject(PhabricatorProject $project) {
$this->project = $project; $this->project = $project;
@ -17,7 +18,10 @@ abstract class PhabricatorProjectController extends PhabricatorController {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$request = $this->getRequest(); $request = $this->getRequest();
$id = $request->getURIData('id'); $id = nonempty(
$request->getURIData('projectID'),
$request->getURIData('id'));
$slug = $request->getURIData('slug'); $slug = $request->getURIData('slug');
if ($slug) { if ($slug) {
@ -80,101 +84,33 @@ abstract class PhabricatorProjectController extends PhabricatorController {
} }
public function buildApplicationMenu() { public function buildApplicationMenu() {
return $this->buildSideNavView(true)->getMenu(); $menu = $this->newApplicationMenu();
$profile_menu = $this->getProfileMenu();
if ($profile_menu) {
$menu->setProfileMenu($profile_menu);
}
$menu->setSearchEngine(new PhabricatorProjectSearchEngine());
return $menu;
} }
public function buildSideNavView($for_app = false) { protected function getProfileMenu() {
$project = $this->getProject(); if (!$this->profileMenu) {
$project = $this->getProject();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$viewer = $this->getViewer();
$id = null;
if ($for_app) {
if ($project) { if ($project) {
$id = $project->getID(); $viewer = $this->getViewer();
$nav->addFilter("profile/{$id}/", pht('Profile'));
$nav->addFilter("board/{$id}/", pht('Workboard')); $engine = id(new PhabricatorProfilePanelEngine())
$nav->addFilter("members/{$id}/", pht('Members')); ->setViewer($viewer)
$nav->addFilter("feed/{$id}/", pht('Feed')); ->setProfileObject($project);
$this->profileMenu = $engine->buildNavigation();
} }
$nav->addFilter('create', pht('Create Project'));
} }
if (!$id) { return $this->profileMenu;
id(new PhabricatorProjectSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
}
$nav->selectFilter(null);
return $nav;
}
public function buildIconNavView(PhabricatorProject $project) {
$this->setProject($project);
$viewer = $this->getViewer();
$id = $project->getID();
$picture = $project->getProfileImageURI();
$name = $project->getName();
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID()))
->execute();
if ($columns) {
$board_icon = 'fa-columns';
} else {
$board_icon = 'fa-columns grey';
}
$nav = new AphrontSideNavFilterView();
$nav->setIconNav(true);
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$nav->addIcon("profile/{$id}/", $name, null, $picture);
$class = 'PhabricatorManiphestApplication';
if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
$phid = $project->getPHID();
$nav->addIcon("board/{$id}/", pht('Workboard'), $board_icon);
$query_uri = urisprintf(
'/maniphest/?statuses=open()&projects=%s#R',
$phid);
$nav->addIcon(null, pht('Open Tasks'), 'fa-anchor', null, $query_uri);
}
$nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o');
$nav->addIcon("members/{$id}/", pht('Members'), 'fa-group');
if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
if ($project->supportsSubprojects()) {
$subprojects_icon = 'fa-sitemap';
} else {
$subprojects_icon = 'fa-sitemap grey';
}
$key = PhabricatorProjectIconSet::getMilestoneIconKey();
$milestones_icon = PhabricatorProjectIconSet::getIconIcon($key);
if (!$project->supportsMilestones()) {
$milestones_icon = "{$milestones_icon} grey";
}
$nav->addIcon(
"subprojects/{$id}/",
pht('Subprojects'),
$subprojects_icon);
$nav->addIcon(
"milestones/{$id}/",
pht('Milestones'),
$milestones_icon);
}
return $nav;
} }
protected function buildApplicationCrumbs() { protected function buildApplicationCrumbs() {

View file

@ -21,6 +21,8 @@ final class PhabricatorProjectEditPictureController
return new Aphront404Response(); return new Aphront404Response();
} }
$this->setProject($project);
$edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
@ -280,15 +282,16 @@ final class PhabricatorProjectEditPictureController
->setHeaderText(pht('Upload New Picture')) ->setHeaderText(pht('Upload New Picture'))
->setForm($upload_form); ->setForm($upload_form);
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->selectFilter("edit/{$id}/"); $nav->selectFilter(PhabricatorProject::PANEL_PROFILE);
$nav->appendChild($form_box);
$nav->appendChild($upload_box);
return $this->buildApplicationPage( return $this->newPage()
$nav, ->setTitle($title)
array( ->setNavigation($nav)
'title' => $title, ->appendChild(
)); array(
$form_box,
$upload_box,
));
} }
} }

View file

@ -33,8 +33,8 @@ final class PhabricatorProjectFeedController
->setHeaderText(pht('Project Activity')) ->setHeaderText(pht('Project Activity'))
->appendChild($feed); ->appendChild($feed);
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->selectFilter("feed/{$id}/"); $nav->selectFilter('feed');
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Feed')); $crumbs->addTextCrumb(pht('Feed'));

View file

@ -8,19 +8,9 @@ final class PhabricatorProjectListController
} }
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer(); return id(new PhabricatorProjectSearchEngine())
$query_key = $request->getURIData('queryKey'); ->setController($this)
->buildResponse();
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($query_key)
->setSearchEngine(new PhabricatorProjectSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function buildApplicationMenu() {
return $this->buildSideNavView(true)->getMenu();
} }
protected function buildApplicationCrumbs() { protected function buildApplicationCrumbs() {

View file

@ -17,6 +17,8 @@ final class PhabricatorProjectMembersEditController
return new Aphront404Response(); return new Aphront404Response();
} }
$this->setProject($project);
$member_phids = $project->getMemberPHIDs(); $member_phids = $project->getMemberPHIDs();
if ($request->isFormPost()) { if ($request->isFormPost()) {
@ -95,8 +97,8 @@ final class PhabricatorProjectMembersEditController
$member_list = $this->renderMemberList($project, $handles); $member_list = $this->renderMemberList($project, $handles);
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->selectFilter("members/{$id}/"); $nav->selectFilter(PhabricatorProject::PANEL_MEMBERS);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Members')); $crumbs->addTextCrumb(pht('Members'));

View file

@ -76,8 +76,8 @@ final class PhabricatorProjectMilestonesController
->setProjects($milestones) ->setProjects($milestones)
->renderList()); ->renderList());
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->selectFilter("milestones/{$id}/"); $nav->selectFilter(PhabricatorProject::PANEL_MILESTONES);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Milestones')); $crumbs->addTextCrumb(pht('Milestones'));

View file

@ -0,0 +1,21 @@
<?php
final class PhabricatorProjectPanelController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadProject();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$project = $this->getProject();
return id(new PhabricatorProfilePanelEngine())
->setProfileObject($project)
->setController($this)
->buildResponse();
}
}

View file

@ -43,8 +43,8 @@ final class PhabricatorProjectProfileController
new PhabricatorProjectTransactionQuery()); new PhabricatorProjectTransactionQuery());
$timeline->setShouldTerminate(true); $timeline->setShouldTerminate(true);
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->selectFilter("profile/{$id}/"); $nav->selectFilter(PhabricatorProject::PANEL_PROFILE);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();

View file

@ -75,8 +75,8 @@ final class PhabricatorProjectSubprojectsController
->setProjects($subprojects) ->setProjects($subprojects)
->renderList()); ->renderList());
$nav = $this->buildIconNavView($project); $nav = $this->getProfileMenu();
$nav->selectFilter("subprojects/{$id}/"); $nav->selectFilter(PhabricatorProject::PANEL_SUBPROJECTS);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Subprojects')); $crumbs->addTextCrumb(pht('Subprojects'));

View file

@ -0,0 +1,61 @@
<?php
final class PhabricatorProjectDetailsProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.details';
public function getPanelTypeName() {
return pht('Project Details');
}
private function getDefaultName() {
return pht('Project Details');
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$project = $config->getProfileObject();
$id = $project->getID();
$picture = $project->getProfileImageURI();
$name = $project->getName();
$href = "/project/profile/{$id}/";
$item = id(new PHUIListItemView())
->setRenderNameAsTooltip(true)
->setType(PHUIListItemView::TYPE_ICON_NAV)
->setHref($href)
->setName($name)
->setProfileImage($picture);
return array(
$item,
);
}
}

View file

@ -0,0 +1,61 @@
<?php
final class PhabricatorProjectMembersProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.members';
public function getPanelTypeName() {
return pht('Project Members');
}
private function getDefaultName() {
return pht('Members');
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$project = $config->getProfileObject();
$id = $project->getID();
$name = $this->getDisplayName($config);
$icon = 'fa-group';
$href = "/project/members/{$id}/";
$item = id(new PHUIListItemView())
->setRenderNameAsTooltip(true)
->setType(PHUIListItemView::TYPE_ICON_NAV)
->setHref($href)
->setName($name)
->setIcon($icon);
return array(
$item,
);
}
}

View file

@ -0,0 +1,76 @@
<?php
final class PhabricatorProjectWorkboardProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.workboard';
public function getPanelTypeName() {
return pht('Project Workboard');
}
private function getDefaultName() {
return pht('Workboard');
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$viewer = $this->getViewer();
// Workboards are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return array();
}
$project = $config->getProfileObject();
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID()))
->execute();
if ($columns) {
$icon = 'fa-columns';
} else {
$icon = 'fa-columns grey';
}
$id = $project->getID();
$href = "/project/board/{$id}/";
$name = $this->getDisplayName($config);
$item = id(new PHUIListItemView())
->setRenderNameAsTooltip(true)
->setType(PHUIListItemView::TYPE_ICON_NAV)
->setHref($href)
->setName($name)
->setIcon($icon);
return array(
$item,
);
}
}

View file

@ -32,7 +32,7 @@ final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule {
// controlling and these names should parse correctly. // controlling and these names should parse correctly.
// These characters may never appear anywhere in a hashtag. // These characters may never appear anywhere in a hashtag.
$never = '\s?!,:;{}#\\(\\)"\''; $never = '\s?!,:;{}#\\(\\)"\'\\*/~';
// These characters may not appear at the edge of the string. // These characters may not appear at the edge of the string.
$never_edge = '.'; $never_edge = '.';

View file

@ -125,6 +125,16 @@ final class ProjectRemarkupRuleTestCase extends PhabricatorTestCase {
), ),
), ),
'**#orbital**' => array(
'embed' => array(),
'ref' => array(
array(
'offset' => 3,
'id' => 'orbital',
),
),
),
); );
foreach ($cases as $input => $expect) { foreach ($cases as $input => $expect) {

View file

@ -10,7 +10,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
PhabricatorCustomFieldInterface, PhabricatorCustomFieldInterface,
PhabricatorDestructibleInterface, PhabricatorDestructibleInterface,
PhabricatorFulltextInterface, PhabricatorFulltextInterface,
PhabricatorConduitResultInterface { PhabricatorConduitResultInterface,
PhabricatorProfilePanelInterface {
protected $name; protected $name;
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
@ -49,6 +50,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken'; const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
const PANEL_PROFILE = 'project.profile';
const PANEL_WORKBOARD = 'project.workboard';
const PANEL_MEMBERS = 'project.members';
const PANEL_MILESTONES = 'project.milestones';
const PANEL_SUBPROJECTS = 'project.subprojects';
public static function initializeNewProject(PhabricatorUser $actor) { public static function initializeNewProject(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery()) $app = id(new PhabricatorApplicationQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
@ -644,4 +651,47 @@ final class PhabricatorProject extends PhabricatorProjectDAO
return array(); return array();
} }
/* -( PhabricatorProfilePanelInterface )----------------------------------- */
public function getBuiltinProfilePanels() {
$panels = array();
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey(self::PANEL_PROFILE)
->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY);
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey(self::PANEL_WORKBOARD)
->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY);
// TODO: This is temporary.
$uri = urisprintf(
'/maniphest/?statuses=open()&projects=%s#R',
$this->getPHID());
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey('tasks')
->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY)
->setPanelProperty('icon', 'maniphest')
->setPanelProperty('name', pht('Open Tasks'))
->setPanelProperty('uri', $uri);
// TODO: This is temporary.
$id = $this->getID();
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey('feed')
->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY)
->setPanelProperty('icon', 'feed')
->setPanelProperty('name', pht('Feed'))
->setPanelProperty('uri', "/project/feed/{$id}/");
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey(self::PANEL_MEMBERS)
->setPanelKey(PhabricatorProjectMembersProfilePanel::PANELKEY);
return $panels;
}
} }

View file

@ -63,10 +63,16 @@ final class PhabricatorProjectLogicalViewerDatasource
$phids = mpull($projects, 'getPHID'); $phids = mpull($projects, 'getPHID');
$results = array(); $results = array();
foreach ($phids as $phid) { if ($phids) {
foreach ($phids as $phid) {
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_OR,
$phid);
}
} else {
$results[] = new PhabricatorQueryConstraint( $results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_OR, PhabricatorQueryConstraint::OPERATOR_EMPTY,
$phid); null);
} }
return $results; return $results;

View file

@ -10,12 +10,7 @@ final class ReleephDefaultFieldSelector extends ReleephFieldSelector {
* as possible. This obivously is an abomination. -epriestley * as possible. This obivously is an abomination. -epriestley
*/ */
public static function isFacebook() { public static function isFacebook() {
try { return class_exists('ReleephFacebookKarmaFieldSpecification');
class_exists('ReleephFacebookKarmaFieldSpecification');
return true;
} catch (Exception $ex) {
return false;
}
} }
/** /**

View file

@ -63,7 +63,7 @@ final class RepositoryQueryConduitAPIMethod
$remote_uris = $request->getValue('remoteURIs', array()); $remote_uris = $request->getValue('remoteURIs', array());
if ($remote_uris) { if ($remote_uris) {
$query->withRemoteURIs($remote_uris); $query->withURIs($remote_uris);
} }
$uuids = $request->getValue('uuids', array()); $uuids = $request->getValue('uuids', array());

View file

@ -39,7 +39,7 @@ final class PhabricatorRepositoryEditor
$types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY; $types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY;
$types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL; $types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL;
$types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS; $types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS;
$types[] = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME; $types[] = PhabricatorRepositoryTransaction::TYPE_SLUG;
$types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE;
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE;
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES;
@ -98,8 +98,8 @@ final class PhabricatorRepositoryEditor
return $object->getCredentialPHID(); return $object->getCredentialPHID();
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
return $object->shouldAllowDangerousChanges(); return $object->shouldAllowDangerousChanges();
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: case PhabricatorRepositoryTransaction::TYPE_SLUG:
return $object->getDetail('clone-name'); return $object->getRepositorySlug();
case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SERVICE:
return $object->getAlmanacServicePHID(); return $object->getAlmanacServicePHID();
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
@ -141,13 +141,18 @@ final class PhabricatorRepositoryEditor
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SERVICE:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
return $xaction->getNewValue(); return $xaction->getNewValue();
case PhabricatorRepositoryTransaction::TYPE_SLUG:
$name = $xaction->getNewValue();
if (strlen($name)) {
return $name;
}
return null;
case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
return (int)$xaction->getNewValue(); return (int)$xaction->getNewValue();
@ -215,8 +220,8 @@ final class PhabricatorRepositoryEditor
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
$object->setDetail('allow-dangerous-changes', $xaction->getNewValue()); $object->setDetail('allow-dangerous-changes', $xaction->getNewValue());
return; return;
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: case PhabricatorRepositoryTransaction::TYPE_SLUG:
$object->setDetail('clone-name', $xaction->getNewValue()); $object->setRepositorySlug($xaction->getNewValue());
return; return;
case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SERVICE:
$object->setAlmanacServicePHID($xaction->getNewValue()); $object->setAlmanacServicePHID($xaction->getNewValue());
@ -326,7 +331,7 @@ final class PhabricatorRepositoryEditor
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: case PhabricatorRepositoryTransaction::TYPE_SLUG:
case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SERVICE:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
@ -448,9 +453,73 @@ final class PhabricatorRepositoryEditor
} }
} }
break; break;
case PhabricatorRepositoryTransaction::TYPE_SLUG:
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!strlen($new)) {
continue;
}
if ($new === $old) {
continue;
}
try {
PhabricatorRepository::asssertValidRepositorySlug($new);
} catch (Exception $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$ex->getMessage(),
$xaction);
continue;
}
$other = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withSlugs(array($new))
->executeOne();
if ($other && ($other->getID() !== $object->getID())) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Duplicate'),
pht(
'The selected repository short name is already in use by '.
'another repository. Choose a unique short name.'),
$xaction);
continue;
}
}
break;
} }
return $errors; return $errors;
} }
protected function didCatchDuplicateKeyException(
PhabricatorLiskDAO $object,
array $xactions,
Exception $ex) {
$errors = array();
$errors[] = new PhabricatorApplicationTransactionValidationError(
null,
pht('Invalid'),
pht(
'The chosen callsign or repository short name is already in '.
'use by another repository.'),
null);
throw new PhabricatorApplicationTransactionValidationException($errors);
}
protected function supportsSearch() {
return true;
}
} }

View file

@ -10,7 +10,7 @@ final class PhabricatorRepositoryRepositoryPHIDType
} }
public function getTypeIcon() { public function getTypeIcon() {
return 'fa-database'; return 'fa-code';
} }
public function newObject() { public function newObject() {

View file

@ -9,13 +9,15 @@ final class PhabricatorRepositoryQuery
private $types; private $types;
private $uuids; private $uuids;
private $nameContains; private $nameContains;
private $remoteURIs; private $uris;
private $datasourceQuery; private $datasourceQuery;
private $slugs;
private $numericIdentifiers; private $numericIdentifiers;
private $callsignIdentifiers; private $callsignIdentifiers;
private $phidIdentifiers; private $phidIdentifiers;
private $monogramIdentifiers; private $monogramIdentifiers;
private $slugIdentifiers;
private $identifierMap; private $identifierMap;
@ -55,26 +57,38 @@ final class PhabricatorRepositoryQuery
$callsigns = array(); $callsigns = array();
$phids = array(); $phids = array();
$monograms = array(); $monograms = array();
$slugs = array();
foreach ($identifiers as $identifier) { foreach ($identifiers as $identifier) {
if (ctype_digit((string)$identifier)) { if (ctype_digit((string)$identifier)) {
$ids[$identifier] = $identifier; $ids[$identifier] = $identifier;
} else if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) { continue;
$monograms[$identifier] = $identifier;
} else {
$repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST;
if (phid_get_type($identifier) === $repository_type) {
$phids[$identifier] = $identifier;
} else {
$callsigns[$identifier] = $identifier;
}
} }
if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) {
$monograms[$identifier] = $identifier;
continue;
}
$repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST;
if (phid_get_type($identifier) === $repository_type) {
$phids[$identifier] = $identifier;
continue;
}
if (preg_match('/^[A-Z]+\z/', $identifier)) {
$callsigns[$identifier] = $identifier;
continue;
}
$slugs[$identifier] = $identifier;
} }
$this->numericIdentifiers = $ids; $this->numericIdentifiers = $ids;
$this->callsignIdentifiers = $callsigns; $this->callsignIdentifiers = $callsigns;
$this->phidIdentifiers = $phids; $this->phidIdentifiers = $phids;
$this->monogramIdentifiers = $monograms; $this->monogramIdentifiers = $monograms;
$this->slugIdentifiers = $slugs;
return $this; return $this;
} }
@ -104,8 +118,8 @@ final class PhabricatorRepositoryQuery
return $this; return $this;
} }
public function withRemoteURIs(array $uris) { public function withURIs(array $uris) {
$this->remoteURIs = $uris; $this->uris = $uris;
return $this; return $this;
} }
@ -114,6 +128,11 @@ final class PhabricatorRepositoryQuery
return $this; return $this;
} }
public function withSlugs(array $slugs) {
$this->slugs = $slugs;
return $this;
}
public function needCommitCounts($need_counts) { public function needCommitCounts($need_counts) {
$this->needCommitCounts = $need_counts; $this->needCommitCounts = $need_counts;
return $this; return $this;
@ -244,17 +263,6 @@ final class PhabricatorRepositoryQuery
} }
} }
// TODO: Denormalize this, too.
if ($this->remoteURIs) {
$try_uris = $this->getNormalizedPaths();
$try_uris = array_fuse($try_uris);
foreach ($repositories as $key => $repository) {
if (!isset($try_uris[$repository->getNormalizedPath()])) {
unset($repositories[$key]);
}
}
}
// Build the identifierMap // Build the identifierMap
if ($this->numericIdentifiers) { if ($this->numericIdentifiers) {
foreach ($this->numericIdentifiers as $id) { foreach ($this->numericIdentifiers as $id) {
@ -299,6 +307,26 @@ final class PhabricatorRepositoryQuery
} }
} }
if ($this->slugIdentifiers) {
$slug_map = array();
foreach ($repositories as $repository) {
$slug = $repository->getRepositorySlug();
if ($slug === null) {
continue;
}
$normal = phutil_utf8_strtolower($slug);
$slug_map[$normal] = $repository;
}
foreach ($this->slugIdentifiers as $slug) {
$normal = phutil_utf8_strtolower($slug);
if (isset($slug_map[$normal])) {
$this->identifierMap[$slug] = $slug_map[$normal];
}
}
}
return $repositories; return $repositories;
} }
@ -406,6 +434,8 @@ final class PhabricatorRepositoryQuery
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
$parts = parent::buildSelectClauseParts($conn); $parts = parent::buildSelectClauseParts($conn);
$parts[] = 'r.*';
if ($this->shouldJoinSummaryTable()) { if ($this->shouldJoinSummaryTable()) {
$parts[] = 's.*'; $parts[] = 's.*';
} }
@ -423,9 +453,28 @@ final class PhabricatorRepositoryQuery
PhabricatorRepository::TABLE_SUMMARY); PhabricatorRepository::TABLE_SUMMARY);
} }
if ($this->shouldJoinURITable()) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T uri ON r.phid = uri.repositoryPHID',
id(new PhabricatorRepositoryURIIndex())->getTableName());
}
return $joins; return $joins;
} }
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinURITable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
private function shouldJoinURITable() {
return ($this->uris !== null);
}
private function shouldJoinSummaryTable() { private function shouldJoinSummaryTable() {
if ($this->needCommitCounts) { if ($this->needCommitCounts) {
return true; return true;
@ -474,7 +523,8 @@ final class PhabricatorRepositoryQuery
if ($this->numericIdentifiers || if ($this->numericIdentifiers ||
$this->callsignIdentifiers || $this->callsignIdentifiers ||
$this->phidIdentifiers || $this->phidIdentifiers ||
$this->monogramIdentifiers) { $this->monogramIdentifiers ||
$this->slugIdentifiers) {
$identifier_clause = array(); $identifier_clause = array();
if ($this->numericIdentifiers) { if ($this->numericIdentifiers) {
@ -525,6 +575,13 @@ final class PhabricatorRepositoryQuery
} }
} }
if ($this->slugIdentifiers) {
$identifier_clause[] = qsprintf(
$conn,
'r.repositorySlug IN (%Ls)',
$this->slugIdentifiers);
}
$where = array('('.implode(' OR ', $identifier_clause).')'); $where = array('('.implode(' OR ', $identifier_clause).')');
} }
@ -545,7 +602,7 @@ final class PhabricatorRepositoryQuery
if (strlen($this->nameContains)) { if (strlen($this->nameContains)) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'name LIKE %~', 'r.name LIKE %~',
$this->nameContains); $this->nameContains);
} }
@ -559,9 +616,27 @@ final class PhabricatorRepositoryQuery
} }
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'r.name LIKE %> OR r.callsign LIKE %>', 'r.name LIKE %> OR r.callsign LIKE %> OR r.repositorySlug LIKE %>',
$query, $query,
$callsign); $callsign,
$query);
}
if ($this->slugs !== null) {
$where[] = qsprintf(
$conn,
'r.repositorySlug IN (%Ls)',
$this->slugs);
}
if ($this->uris !== null) {
$try_uris = $this->getNormalizedPaths();
$try_uris = array_fuse($try_uris);
$where[] = qsprintf(
$conn,
'uri.repositoryURI IN (%Ls)',
$try_uris);
} }
return $where; return $where;
@ -580,7 +655,7 @@ final class PhabricatorRepositoryQuery
// or an `svn+ssh` URI, we could deduce how to normalize it. However, this // or an `svn+ssh` URI, we could deduce how to normalize it. However, this
// would be more complicated and it's not clear if it matters in practice. // would be more complicated and it's not clear if it matters in practice.
foreach ($this->remoteURIs as $uri) { foreach ($this->uris as $uri) {
$normalized_uris[] = new PhabricatorRepositoryURINormalizer( $normalized_uris[] = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_GIT, PhabricatorRepositoryURINormalizer::TYPE_GIT,
$uri); $uri);

View file

@ -160,10 +160,16 @@ final class PhabricatorRepositorySearchEngine
$commit = $repository->getMostRecentCommit(); $commit = $repository->getMostRecentCommit();
if ($commit) { if ($commit) {
$commit_link = DiffusionView::linkCommit( $commit_link = phutil_tag(
$repository, 'a',
$commit->getCommitIdentifier(), array(
$commit->getSummary()); 'href' => $commit->getURI(),
),
pht(
'%s: %s',
$commit->getLocalName(),
$commit->getSummary()));
$item->setSubhead($commit_link); $item->setSubhead($commit_link);
$item->setEpoch($commit->getEpoch()); $item->setEpoch($commit->getEpoch());
} }

View file

@ -46,6 +46,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
protected $name; protected $name;
protected $callsign; protected $callsign;
protected $repositorySlug;
protected $uuid; protected $uuid;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
@ -93,6 +94,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort255', 'name' => 'sort255',
'callsign' => 'sort32', 'callsign' => 'sort32',
'repositorySlug' => 'sort64?',
'versionControlSystem' => 'text32', 'versionControlSystem' => 'text32',
'uuid' => 'text64?', 'uuid' => 'text64?',
'pushPolicy' => 'policy', 'pushPolicy' => 'policy',
@ -100,11 +102,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'almanacServicePHID' => 'phid?', 'almanacServicePHID' => 'phid?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'callsign' => array( 'callsign' => array(
'columns' => array('callsign'), 'columns' => array('callsign'),
'unique' => true, 'unique' => true,
@ -115,6 +112,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'key_vcs' => array( 'key_vcs' => array(
'columns' => array('versionControlSystem'), 'columns' => array('versionControlSystem'),
), ),
'key_slug' => array(
'columns' => array('repositorySlug'),
'unique' => true,
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
@ -297,7 +298,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
* @return string * @return string
*/ */
public function getCloneName() { public function getCloneName() {
$name = $this->getDetail('clone-name'); $name = $this->getRepositorySlug();
// Make some reasonable effort to produce reasonable default directory // Make some reasonable effort to produce reasonable default directory
// names from repository names. // names from repository names.
@ -314,6 +315,82 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return $name; return $name;
} }
public static function isValidRepositorySlug($slug) {
try {
self::asssertValidRepositorySlug($slug);
return true;
} catch (Exception $ex) {
return false;
}
}
public static function asssertValidRepositorySlug($slug) {
if (!strlen($slug)) {
throw new Exception(
pht(
'The empty string is not a valid repository short name. '.
'Repository short names must be at least one character long.'));
}
if (strlen($slug) > 64) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not be longer than 64 characters.',
$slug));
}
if (preg_match('/[^a-zA-Z0-9._-]/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may only contain letters, numbers, periods, hyphens '.
'and underscores.',
$slug));
}
if (!preg_match('/^[a-zA-Z0-9]/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must begin with a letter or number.',
$slug));
}
if (!preg_match('/[a-zA-Z0-9]\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must end with a letter or number.',
$slug));
}
if (preg_match('/__|--|\\.\\./', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not contain multiple consecutive underscores, '.
'hyphens, or periods.',
$slug));
}
if (preg_match('/^[A-Z]+\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may not contain only uppercase letters.',
$slug));
}
if (preg_match('/^\d+\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may not contain only numbers.',
$slug));
}
}
/* -( Remote Command Execution )------------------------------------------- */ /* -( Remote Command Execution )------------------------------------------- */
@ -746,30 +823,40 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return $uri; return $uri;
} }
public function getNormalizedPath() { public function updateURIIndex() {
$uri = (string)$this->getCloneURIObject(); $uris = array(
(string)$this->getCloneURIObject(),
);
foreach ($uris as $key => $uri) {
$uris[$key] = $this->getNormalizedURI($uri)
->getNormalizedPath();
}
PhabricatorRepositoryURIIndex::updateRepositoryURIs(
$this->getPHID(),
$uris);
return $this;
}
private function getNormalizedURI($uri) {
switch ($this->getVersionControlSystem()) { switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$normalized_uri = new PhabricatorRepositoryURINormalizer( return new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_GIT, PhabricatorRepositoryURINormalizer::TYPE_GIT,
$uri); $uri);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$normalized_uri = new PhabricatorRepositoryURINormalizer( return new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_SVN, PhabricatorRepositoryURINormalizer::TYPE_SVN,
$uri); $uri);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$normalized_uri = new PhabricatorRepositoryURINormalizer( return new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
$uri); $uri);
break;
default: default:
throw new Exception(pht('Unrecognized version control system.')); throw new Exception(pht('Unrecognized version control system.'));
} }
return $normalized_uri->getNormalizedPath();
} }
public function isTracked() { public function isTracked() {
@ -847,7 +934,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return $this->isBranchInFilter($branch, 'branch-filter'); return $this->isBranchInFilter($branch, 'branch-filter');
} }
public function formatCommitName($commit_identifier) { public function formatCommitName($commit_identifier, $local = false) {
$vcs = $this->getVersionControlSystem(); $vcs = $this->getVersionControlSystem();
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
@ -856,12 +943,23 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
$is_git = ($vcs == $type_git); $is_git = ($vcs == $type_git);
$is_hg = ($vcs == $type_hg); $is_hg = ($vcs == $type_hg);
if ($is_git || $is_hg) { if ($is_git || $is_hg) {
$short_identifier = substr($commit_identifier, 0, 12); $name = substr($commit_identifier, 0, 12);
$need_scope = false;
} else { } else {
$short_identifier = $commit_identifier; $name = $commit_identifier;
$need_scope = true;
} }
return 'r'.$this->getCallsign().$short_identifier; if (!$local) {
$need_scope = true;
}
if ($need_scope) {
$scope = 'r'.$this->getCallsign();
$name = $scope.$name;
}
return $name;
} }
public function isImporting() { public function isImporting() {
@ -2143,13 +2241,17 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
public function destroyObjectPermanently( public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) { PhabricatorDestructionEngine $engine) {
$phid = $this->getPHID();
$this->openTransaction(); $this->openTransaction();
$this->delete(); $this->delete();
PhabricatorRepositoryURIIndex::updateRepositoryURIs($phid, array());
$books = id(new DivinerBookQuery()) $books = id(new DivinerBookQuery())
->setViewer($engine->getViewer()) ->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($this->getPHID())) ->withRepositoryPHIDs(array($phid))
->execute(); ->execute();
foreach ($books as $book) { foreach ($books as $book) {
$engine->destroyObject($book); $engine->destroyObject($book);
@ -2157,7 +2259,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
$atoms = id(new DivinerAtomQuery()) $atoms = id(new DivinerAtomQuery())
->setViewer($engine->getViewer()) ->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($this->getPHID())) ->withRepositoryPHIDs(array($phid))
->execute(); ->execute();
foreach ($atoms as $atom) { foreach ($atoms as $atom) {
$engine->destroyObject($atom); $engine->destroyObject($atom);

View file

@ -266,9 +266,20 @@ final class PhabricatorRepositoryCommit
return $repository->formatCommitName($identifier); return $repository->formatCommitName($identifier);
} }
public function getShortName() { /**
* Return a local display name for use in the context of the containing
* repository.
*
* In Git and Mercurial, this returns only a short hash, like "abcdef012345".
* See @{method:getDisplayName} for a short name that always includes
* repository context.
*
* @return string Short human-readable name for use inside a repository.
*/
public function getLocalName() {
$repository = $this->getRepository();
$identifier = $this->getCommitIdentifier(); $identifier = $this->getCommitIdentifier();
return substr($identifier, 0, 9); return $repository->formatCommitName($identifier, $local = true);
} }
public function renderAuthorLink($handles) { public function renderAuthorLink($handles) {

View file

@ -23,7 +23,7 @@ final class PhabricatorRepositoryTransaction
const TYPE_PUSH_POLICY = 'repo:push-policy'; const TYPE_PUSH_POLICY = 'repo:push-policy';
const TYPE_CREDENTIAL = 'repo:credential'; const TYPE_CREDENTIAL = 'repo:credential';
const TYPE_DANGEROUS = 'repo:dangerous'; const TYPE_DANGEROUS = 'repo:dangerous';
const TYPE_CLONE_NAME = 'repo:clone-name'; const TYPE_SLUG = 'repo:slug';
const TYPE_SERVICE = 'repo:service'; const TYPE_SERVICE = 'repo:service';
const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source'; const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source';
const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language'; const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language';
@ -369,19 +369,19 @@ final class PhabricatorRepositoryTransaction
'%s enabled protection against dangerous changes.', '%s enabled protection against dangerous changes.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
} }
case self::TYPE_CLONE_NAME: case self::TYPE_SLUG:
if (strlen($old) && !strlen($new)) { if (strlen($old) && !strlen($new)) {
return pht( return pht(
'%s removed the clone name of this repository.', '%s removed the short name of this repository.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
} else if (strlen($new) && !strlen($old)) { } else if (strlen($new) && !strlen($old)) {
return pht( return pht(
'%s set the clone name of this repository to "%s".', '%s set the short name of this repository to "%s".',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$new); $new);
} else { } else {
return pht( return pht(
'%s changed the clone name of this repository from "%s" to "%s".', '%s changed the short name of this repository from "%s" to "%s".',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$old, $old,
$new); $new);

View file

@ -0,0 +1,67 @@
<?php
final class PhabricatorRepositoryURIIndex
extends PhabricatorRepositoryDAO {
protected $repositoryPHID;
protected $repositoryURI;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'repositoryURI' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'key_repository' => array(
'columns' => array('repositoryPHID'),
),
'key_uri' => array(
'columns' => array('repositoryURI(128)'),
),
),
) + parent::getConfiguration();
}
public static function updateRepositoryURIs(
$repository_phid,
array $uris) {
$table = new self();
$conn_w = $table->establishConnection('w');
$sql = array();
foreach ($uris as $key => $uri) {
if (!strlen($uri)) {
unset($uris[$key]);
continue;
}
$sql[] = qsprintf(
$conn_w,
'(%s, %s)',
$repository_phid,
$uri);
}
$table->openTransaction();
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryPHID = %s',
$table->getTableName(),
$repository_phid);
if ($sql) {
queryfx(
$conn_w,
'INSERT INTO %T (repositoryPHID, repositoryURI) VALUES %Q',
$table->getTableName(),
implode(', ', $sql));
}
$table->saveTransaction();
}
}

View file

@ -152,4 +152,68 @@ final class PhabricatorRepositoryTestCase
} }
} }
public function testRepositoryShortNameValidation() {
$good = array(
'sensible-repository',
'AReasonableName',
'ACRONYM-project',
'sol-123',
'46-helixes',
'node.io',
'internet.com',
'www.internet-site.com.repository',
'with_under-scores',
// Can't win them all.
'A-_._-_._-_._-_._-_._-_._-1',
// 64-character names are fine.
str_repeat('a', 64),
);
$poor = array(
'',
'1',
'.',
'-_-',
'AAAA',
'..',
'a/b',
'../../etc/passwd',
'/',
'!',
'@',
'ca$hmoney',
'repo with spaces',
'hyphen-',
'-ated',
'_underscores_',
'yes!',
// 65-character names are no good.
str_repeat('a', 65),
);
foreach ($good as $nice_name) {
$actual = PhabricatorRepository::isValidRepositorySlug($nice_name);
$this->assertEqual(
true,
$actual,
pht(
'Expected "%s" to be a valid repository short name.',
$nice_name));
}
foreach ($poor as $poor_name) {
$actual = PhabricatorRepository::isValidRepositorySlug($poor_name);
$this->assertEqual(
false,
$actual,
pht(
'Expected "%s" to be rejected as an invalid repository '.
'short name.',
$poor_name));
}
}
} }

View file

@ -58,6 +58,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker
->setMaximumBytes(255) ->setMaximumBytes(255)
->truncateString((string)$author)); ->truncateString((string)$author));
$data->setCommitDetail('authorEpoch', $ref->getAuthorEpoch());
$data->setCommitDetail('authorName', $ref->getAuthorName()); $data->setCommitDetail('authorName', $ref->getAuthorName());
$data->setCommitDetail('authorEmail', $ref->getAuthorEmail()); $data->setCommitDetail('authorEmail', $ref->getAuthorEmail());

View file

@ -54,7 +54,7 @@ final class PhabricatorSearchHovercardController
$cards = array(); $cards = array();
foreach ($phids as $phid) { foreach ($phids as $phid) {
$handle = $handles[$phid]; $handle = $handles[$phid];
$object = $objects[$phid]; $object = idx($objects, $phid);
$hovercard = id(new PhabricatorHovercardView()) $hovercard = id(new PhabricatorHovercardView())
->setUser($viewer) ->setUser($viewer)

View file

@ -0,0 +1,136 @@
<?php
final class PhabricatorProfilePanelEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'search.profilepanel';
private $panelEngine;
private $profileObject;
private $newPanelConfiguration;
private $isBuiltin;
public function isEngineConfigurable() {
return false;
}
public function setPanelEngine(PhabricatorProfilePanelEngine $engine) {
$this->panelEngine = $engine;
return $this;
}
public function getPanelEngine() {
return $this->panelEngine;
}
public function setProfileObject(
PhabricatorProfilePanelInterface $profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->profileObject;
}
public function setNewPanelConfiguration(
PhabricatorProfilePanelConfiguration $configuration) {
$this->newPanelConfiguration = $configuration;
return $this;
}
public function getNewPanelConfiguration() {
return $this->newPanelConfiguration;
}
public function setIsBuiltin($is_builtin) {
$this->isBuiltin = $is_builtin;
return $this;
}
public function getIsBuiltin() {
return $this->isBuiltin;
}
public function getEngineName() {
return pht('Profile Panels');
}
public function getSummaryHeader() {
return pht('Edit Profile Panel Configurations');
}
public function getSummaryText() {
return pht('This engine is used to modify menu items on profiles.');
}
public function getEngineApplicationClass() {
return 'PhabricatorSearchApplication';
}
protected function newEditableObject() {
if (!$this->newPanelConfiguration) {
throw new Exception(
pht('Profile panels can not be generated without an object context.'));
}
return clone $this->newPanelConfiguration;
}
protected function newObjectQuery() {
return id(new PhabricatorProfilePanelConfigurationQuery());
}
protected function getObjectCreateTitleText($object) {
if ($this->getIsBuiltin()) {
return pht('Edit Builtin Item');
} else {
return pht('Create Menu Item');
}
}
protected function getObjectCreateButtonText($object) {
if ($this->getIsBuiltin()) {
return pht('Save Changes');
} else {
return pht('Create Menu Item');
}
}
protected function getObjectEditTitleText($object) {
return pht('Edit Menu Item: %s', $object->getDisplayName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Menu Item');
}
protected function getObjectCreateShortText() {
return pht('Edit Menu Item');
}
protected function getObjectCreateCancelURI($object) {
return $this->getPanelEngine()->getConfigureURI();
}
protected function getObjectViewURI($object) {
return $this->getPanelEngine()->getConfigureURI();
}
protected function buildCustomEditFields($object) {
$panel = $object->getPanel();
$fields = $panel->buildEditEngineFields($object);
$type_property =
PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY;
foreach ($fields as $field) {
$field
->setTransactionType($type_property)
->setMetadataValue('property.key', $field->getKey());
}
return $fields;
}
}

View file

@ -0,0 +1,87 @@
<?php
final class PhabricatorProfilePanelEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorSearchApplication';
}
public function getEditorObjectsDescription() {
return pht('Profile Panels');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY;
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER;
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
$key = $xaction->getMetadataValue('property.key');
return $object->getPanelProperty($key, null);
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
return $object->getPanelOrder();
case PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY:
return $object->getVisibility();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
case PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY:
return $xaction->getNewValue();
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
return (int)$xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
$key = $xaction->getMetadataValue('property.key');
$value = $xaction->getNewValue();
$object->setPanelProperty($key, $value);
return;
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
$object->setPanelOrder($xaction->getNewValue());
return;
case PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY:
$object->setVisibility($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
case PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
}

View file

@ -0,0 +1,650 @@
<?php
final class PhabricatorProfilePanelEngine extends Phobject {
private $viewer;
private $profileObject;
private $panels;
private $controller;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setProfileObject(
PhabricatorProfilePanelInterface $profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->profileObject;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function buildResponse() {
$controller = $this->getController();
$viewer = $controller->getViewer();
$this->setViewer($viewer);
$request = $controller->getRequest();
$panel_action = $request->getURIData('panelAction');
$panel_id = $request->getURIData('panelID');
$panel_list = $this->loadPanels();
$selected_panel = null;
if (strlen($panel_id)) {
$panel_id_int = (int)$panel_id;
foreach ($panel_list as $panel) {
if ($panel_id_int) {
if ((int)$panel->getID() === $panel_id_int) {
$selected_panel = $panel;
break;
}
}
$builtin_key = $panel->getBuiltinKey();
if ($builtin_key === (string)$panel_id) {
$selected_panel = $panel;
break;
}
}
}
switch ($panel_action) {
case 'view':
case 'info':
case 'hide':
case 'builtin':
if (!$selected_panel) {
return new Aphront404Response();
}
break;
}
$navigation = $this->buildNavigation();
$navigation->selectFilter('panel.configure');
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
switch ($panel_action) {
case 'view':
$content = $this->buildPanelViewContent($selected_panel);
break;
case 'configure':
$content = $this->buildPanelConfigureContent($panel_list);
$crumbs->addTextCrumb(pht('Configure Menu'));
break;
case 'reorder':
$content = $this->buildPanelReorderContent($panel_list);
break;
case 'new':
$panel_key = $request->getURIData('panelKey');
$content = $this->buildPanelNewContent($panel_key);
break;
case 'builtin':
$content = $this->buildPanelBuiltinContent($selected_panel);
break;
case 'hide':
$content = $this->buildPanelHideContent($selected_panel);
break;
case 'edit':
$content = $this->buildPanelEditContent();
break;
default:
throw new Exception(
pht(
'Unsupported panel action "%s".',
$panel_action));
}
if ($content instanceof AphrontResponse) {
return $content;
}
if ($content instanceof AphrontResponseProducerInterface) {
return $content;
}
return $controller->newPage()
->setTitle(pht('Profile Stuff'))
->setNavigation($navigation)
->setCrumbs($crumbs)
->appendChild($content);
}
public function buildNavigation() {
$nav = id(new AphrontSideNavFilterView())
->setIconNav(true)
->setBaseURI(new PhutilURI('/project/'));
$panels = $this->getPanels();
foreach ($panels as $panel) {
if ($panel->isDisabled()) {
continue;
}
$items = $panel->buildNavigationMenuItems();
foreach ($items as $item) {
$this->validateNavigationMenuItem($item);
}
// If the panel produced only a single item which does not otherwise
// have a key, try to automatically assign it a reasonable key. This
// makes selecting the correct item simpler.
if (count($items) == 1) {
$item = head($items);
if ($item->getKey() === null) {
$builtin_key = $panel->getBuiltinKey();
$panel_phid = $panel->getPHID();
if ($builtin_key !== null) {
$item->setKey($builtin_key);
} else if ($panel_phid !== null) {
$item->setKey($panel_phid);
}
}
}
foreach ($items as $item) {
$nav->addMenuItem($item);
}
}
$configure_item = $this->newConfigureMenuItem();
if ($configure_item) {
$nav->addMenuItem($configure_item);
}
$nav->selectFilter(null);
return $nav;
}
private function getPanels() {
if ($this->panels === null) {
$this->panels = $this->loadPanels();
}
return $this->panels;
}
private function loadPanels() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$panels = $this->loadBuiltinProfilePanels();
$stored_panels = id(new PhabricatorProfilePanelConfigurationQuery())
->setViewer($viewer)
->withProfilePHIDs(array($object->getPHID()))
->execute();
// Merge the stored panels into the builtin panels. If a builtin panel has
// a stored version, replace the defaults with the stored changes.
foreach ($stored_panels as $stored_panel) {
$builtin_key = $stored_panel->getBuiltinKey();
if ($builtin_key !== null) {
$panels[$builtin_key] = $stored_panel;
} else {
$panels[] = $stored_panel;
}
}
foreach ($panels as $panel) {
$impl = $panel->getPanel();
$impl->setViewer($viewer);
}
$panels = msort($panels, 'getSortKey');
// Normalize keys since callers shouldn't rely on this array being
// partially keyed.
$panels = array_values($panels);
return $panels;
}
private function loadBuiltinProfilePanels() {
$object = $this->getProfileObject();
$builtins = $object->getBuiltinProfilePanels();
$panels = PhabricatorProfilePanel::getAllPanels();
$order = 1;
$map = array();
foreach ($builtins as $builtin) {
$builtin_key = $builtin->getBuiltinKey();
if (!$builtin_key) {
throw new Exception(
pht(
'Object produced a builtin panel with no builtin panel key! '.
'Builtin panels must have a unique key.'));
}
if (isset($map[$builtin_key])) {
throw new Exception(
pht(
'Object produced two panels with the same builtin key ("%s"). '.
'Each panel must have a unique builtin key.',
$builtin_key));
}
$panel_key = $builtin->getPanelKey();
$panel = idx($panels, $panel_key);
if (!$panel) {
throw new Exception(
pht(
'Builtin panel ("%s") specifies a bad panel key ("%s"); there '.
'is no corresponding panel implementation available.',
$builtin_key,
$panel_key));
}
$builtin
->setProfilePHID($object->getPHID())
->attachPanel($panel)
->attachProfileObject($object)
->setPanelOrder($order);
$map[$builtin_key] = $builtin;
$order++;
}
return $map;
}
private function validateNavigationMenuItem($item) {
if (!($item instanceof PHUIListItemView)) {
throw new Exception(
pht(
'Expected buildNavigationMenuItems() to return a list of '.
'PHUIListItemView objects, but got a surprise.'));
}
}
private function newConfigureMenuItem() {
if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
return null;
}
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PHUIListItemView())
->setName('Configure Menu')
->setKey('panel.configure')
->setIcon('fa-gear')
->setHref($this->getPanelURI('configure/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setRenderNameAsTooltip(true);
}
public function getConfigureURI() {
return $this->getPanelURI('configure/');
}
private function getPanelURI($path) {
$project = $this->getProfileObject();
$id = $project->getID();
return "/project/{$id}/panel/{$path}";
}
private function buildPanelReorderContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$controller = $this->getController();
$request = $controller->getRequest();
$request->validateCSRF();
$order = $request->getStrList('order');
$by_builtin = array();
$by_id = array();
foreach ($panels as $key => $panel) {
$id = $panel->getID();
if ($id) {
$by_id[$id] = $key;
continue;
}
$builtin_key = $panel->getBuiltinKey();
if ($builtin_key) {
$by_builtin[$builtin_key] = $key;
continue;
}
}
$key_order = array();
foreach ($order as $order_item) {
if (isset($by_id[$order_item])) {
$key_order[] = $by_id[$order_item];
continue;
}
if (isset($by_builtin[$order_item])) {
$key_order[] = $by_builtin[$order_item];
continue;
}
}
$panels = array_select_keys($panels, $key_order) + $panels;
$type_order =
PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER;
$order = 1;
foreach ($panels as $panel) {
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_order)
->setNewValue($order);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($panel, $xactions);
$order++;
}
return id(new AphrontRedirectResponse())
->setURI($this->getConfigureURI());
}
private function buildPanelConfigureContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$list_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'reorder-profile-menu-items',
array(
'listID' => $list_id,
'orderURI' => $this->getPanelURI('reorder/'),
));
$list = id(new PHUIObjectItemListView())
->setID($list_id);
foreach ($panels as $panel) {
$id = $panel->getID();
$builtin_key = $panel->getBuiltinKey();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$panel,
PhabricatorPolicyCapability::CAN_EDIT);
$item = id(new PHUIObjectItemView());
$name = $panel->getDisplayName();
$type = $panel->getPanelTypeName();
if (!strlen(trim($name))) {
$name = pht('Untitled "%s" Item', $type);
}
$item->setHeader($name);
$item->addAttribute($type);
if ($can_edit) {
$item
->setGrippable(true)
->addSigil('profile-menu-item')
->setMetadata(
array(
'key' => nonempty($id, $builtin_key),
));
if ($id) {
$item->setHref($this->getPanelURI("edit/{$id}/"));
$hide_uri = $this->getPanelURI("hide/{$id}/");
} else {
$item->setHref($this->getPanelURI("builtin/{$builtin_key}/"));
$hide_uri = $this->getPanelURI("hide/{$builtin_key}/");
}
$item->addAction(
id(new PHUIListItemView())
->setHref($hide_uri)
->setWorkflow(true)
->setIcon(pht('fa-eye')));
}
if ($panel->isDisabled()) {
$item->setDisabled(true);
$item->addIcon('fa-times grey', pht('Disabled'));
}
$list->addItem($item);
}
$action_view = id(new PhabricatorActionListView())
->setUser($viewer);
$panel_types = PhabricatorProfilePanel::getAllPanels();
$action_view->addAction(
id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Add New Menu Item...')));
foreach ($panel_types as $panel_type) {
if (!$panel_type->canAddToObject($object)) {
continue;
}
$panel_key = $panel_type->getPanelKey();
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon($panel_type->getPanelTypeIcon())
->setName($panel_type->getPanelTypeName())
->setHref($this->getPanelURI("new/{$panel_key}/")));
}
$action_view->addAction(
id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Documentation')));
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon('fa-book')
->setName(pht('TODO: Write Documentation')));
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Configure Menu'))
->setHref('#')
->setIconFont('fa-gear')
->setDropdownMenu($action_view);
$header = id(new PHUIHeaderView())
->setHeader(pht('Profile Menu Items'))
->addActionLink($action_button);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setObjectList($list);
return $box;
}
private function buildPanelNewContent($panel_key) {
$panel_types = PhabricatorProfilePanel::getAllPanels();
$panel_type = idx($panel_types, $panel_key);
if (!$panel_type) {
return new Aphront404Response();
}
$object = $this->getProfileObject();
if (!$panel_type->canAddToObject($object)) {
return new Aphront404Response();
}
$configuration =
PhabricatorProfilePanelConfiguration::initializeNewPanelConfiguration(
$object,
$panel_type);
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setPanelEngine($this)
->setProfileObject($object)
->setNewPanelConfiguration($configuration)
->setController($controller)
->buildResponse();
}
private function buildPanelEditContent() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setPanelEngine($this)
->setProfileObject($object)
->setController($controller)
->buildResponse();
}
private function buildPanelBuiltinContent(
PhabricatorProfilePanelConfiguration $configuration) {
// If this builtin panel has already been persisted, redirect to the
// edit page.
$id = $configuration->getID();
if ($id) {
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI("edit/{$id}/"));
}
// Otherwise, act like we're creating a new panel, we're just starting
// with the builtin template.
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$object = $this->getProfileObject();
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setIsBuiltin(true)
->setPanelEngine($this)
->setProfileObject($object)
->setNewPanelConfiguration($configuration)
->setController($controller)
->buildResponse();
}
private function buildPanelHideContent(
PhabricatorProfilePanelConfiguration $configuration) {
$controller = $this->getController();
$request = $controller->getRequest();
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$v_visibility = $configuration->getVisibility();
if ($request->isFormPost()) {
$v_visibility = $request->getStr('visibility');
$type_visibility =
PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_visibility)
->setNewValue($v_visibility);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($configuration, $xactions);
return id(new AphrontRedirectResponse())
->setURI($this->getConfigureURI());
}
$map = PhabricatorProfilePanelConfiguration::getVisibilityNameMap();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendControl(
id(new AphrontFormSelectControl())
->setName('visibility')
->setLabel(pht('Visibility'))
->setValue($v_visibility)
->setOptions($map));
return $controller->newDialog()
->setTitle(pht('Change Item Visibility'))
->appendForm($form)
->addCancelButton($this->getConfigureURI())
->addSubmitButton(pht('Save Changes'));
}
}

View file

@ -0,0 +1,7 @@
<?php
interface PhabricatorProfilePanelInterface {
public function getBuiltinProfilePanels();
}

View file

@ -0,0 +1,40 @@
<?php
final class PhabricatorProfilePanelPHIDType
extends PhabricatorPHIDType {
const TYPECONST = 'PANL';
public function getTypeName() {
return pht('Profile Panel');
}
public function newObject() {
return new PhabricatorProfilePanelConfiguration();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorSearchApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $object_query,
array $phids) {
return id(new PhabricatorProfilePanelConfigurationQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$config = $objects[$phid];
$handle->setName(pht('Profile Panel'));
$handle->setURI($config->getURI());
}
}
}

View file

@ -0,0 +1,97 @@
<?php
final class PhabricatorLinkProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'link';
public function getPanelTypeIcon() {
return 'fa-link';
}
public function getPanelTypeName() {
return pht('Link');
}
public function canAddToObject(
PhabricatorProfilePanelInterface $object) {
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
return $this->getLinkName($config);
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setIsRequired(true)
->setValue($this->getLinkName($config)),
id(new PhabricatorTextEditField())
->setKey('uri')
->setLabel(pht('URI'))
->setIsRequired(true)
->setValue($this->getLinkURI($config)),
id(new PhabricatorIconSetEditField())
->setKey('icon')
->setLabel(pht('Icon'))
->setIconSet(new PhabricatorProfilePanelIconSet())
->setValue($this->getLinkIcon($config)),
);
}
private function getLinkName(
PhabricatorProfilePanelConfiguration $config) {
return $config->getPanelProperty('name');
}
private function getLinkIcon(
PhabricatorProfilePanelConfiguration $config) {
return $config->getPanelProperty('icon', 'link');
}
private function getLinkURI(
PhabricatorProfilePanelConfiguration $config) {
return $config->getPanelProperty('uri');
}
private function isValidLinkURI($uri) {
return PhabricatorEnv::isValidURIForLink($uri);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$icon = $this->getLinkIcon($config);
$name = $this->getLinkName($config);
$href = $this->getLinkURI($config);
if (!$this->isValidLinkURI($href)) {
$href = '#';
}
$icon_object = id(new PhabricatorProfilePanelIconSet())
->getIcon($icon);
if ($icon_object) {
$icon_class = $icon_object->getIcon();
} else {
$icon_class = 'fa-link';
}
$item = id(new PHUIListItemView())
->setRenderNameAsTooltip(true)
->setType(PHUIListItemView::TYPE_ICON_NAV)
->setHref($href)
->setName($name)
->setIcon($icon_class);
return array(
$item,
);
}
}

View file

@ -0,0 +1,54 @@
<?php
abstract class PhabricatorProfilePanel extends Phobject {
private $viewer;
final public function buildNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
return $this->newNavigationMenuItems($config);
}
abstract protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config);
public function getPanelTypeIcon() {
return null;
}
abstract public function getPanelTypeName();
abstract public function getDisplayName(
PhabricatorProfilePanelConfiguration $config);
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array();
}
public function canAddToObject(
PhabricatorProfilePanelInterface $object) {
return false;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
final public function getPanelKey() {
return $this->getPhobjectClassConstant('PANELKEY');
}
final public static function getAllPanels() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getPanelKey')
->execute();
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorProfilePanelIconSet
extends PhabricatorIconSet {
const ICONSETKEY = 'profilepanel';
public function getSelectIconTitleText() {
return pht('Choose Item Icon');
}
protected function newIcons() {
$list = array(
array(
'key' => 'link',
'icon' => 'fa-link',
'name' => pht('Link'),
),
array(
'key' => 'maniphest',
'icon' => 'fa-anchor',
'name' => pht('Maniphest'),
),
array(
'key' => 'feed',
'icon' => 'fa-newspaper-o',
'name' => pht('Feed'),
),
);
$icons = array();
foreach ($list as $spec) {
$icons[] = id(new PhabricatorIconSetIcon())
->setKey($spec['key'])
->setIcon($spec['icon'])
->setLabel($spec['name']);
}
return $icons;
}
}

View file

@ -0,0 +1,102 @@
<?php
final class PhabricatorProfilePanelConfigurationQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $profilePHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProfilePHIDs(array $phids) {
$this->profilePHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorProfilePanelConfiguration();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->profilePHIDs !== null) {
$where[] = qsprintf(
$conn,
'profilePHID IN (%Ls)',
$this->profilePHIDs);
}
return $where;
}
protected function willFilterPage(array $page) {
$panels = PhabricatorProfilePanel::getAllPanels();
foreach ($page as $key => $panel) {
$panel_type = idx($panels, $panel->getPanelKey());
if (!$panel_type) {
$this->didRejectResult($panel);
unset($page[$key]);
continue;
}
$panel->attachPanel($panel_type);
}
if (!$page) {
return array();
}
$profile_phids = mpull($page, 'getProfilePHID');
$profiles = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($profile_phids)
->execute();
$profiles = mpull($profiles, null, 'getPHID');
foreach ($page as $key => $panel) {
$profile = idx($profiles, $panel->getProfilePHID());
if (!$profile) {
$this->didRejectResult($panel);
unset($page[$key]);
continue;
}
$panel->attachProfileObject($profile);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorSearchApplication';
}
}

View file

@ -0,0 +1,193 @@
<?php
final class PhabricatorProfilePanelConfiguration
extends PhabricatorSearchDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorApplicationTransactionInterface {
protected $profilePHID;
protected $panelKey;
protected $builtinKey;
protected $panelOrder;
protected $visibility;
protected $panelProperties = array();
private $profileObject = self::ATTACHABLE;
private $panel = self::ATTACHABLE;
const VISIBILITY_VISIBLE = 'visible';
const VISIBILITY_DISABLED = 'disabled';
public static function initializeNewBuiltin() {
return id(new self())
->setVisibility(self::VISIBILITY_VISIBLE);
}
public static function initializeNewPanelConfiguration(
PhabricatorProfilePanelInterface $profile_object,
PhabricatorProfilePanel $panel) {
return self::initializeNewBuiltin()
->setProfilePHID($profile_object->getPHID())
->setPanelKey($panel->getPanelKey())
->attachPanel($panel)
->attachProfileObject($profile_object);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'panelProperties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'panelKey' => 'text64',
'builtinKey' => 'text64?',
'panelOrder' => 'uint32?',
'visibility' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_profile' => array(
'columns' => array('profilePHID', 'panelOrder'),
),
),
) + parent::getConfiguration();
}
public static function getVisibilityNameMap() {
return array(
self::VISIBILITY_VISIBLE => pht('Visible'),
self::VISIBILITY_DISABLED => pht('Disabled'),
);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorProfilePanelPHIDType::TYPECONST);
}
public function attachPanel(PhabricatorProfilePanel $panel) {
$this->panel = $panel;
return $this;
}
public function getPanel() {
return $this->assertAttached($this->panel);
}
public function attachProfileObject(
PhabricatorProfilePanelInterface $profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->assertAttached($this->profileObject);
}
public function setPanelProperty($key, $value) {
$this->panelProperties[$key] = $value;
return $this;
}
public function getPanelProperty($key, $default = null) {
return idx($this->panelProperties, $key, $default);
}
public function buildNavigationMenuItems() {
return $this->getPanel()->buildNavigationMenuItems($this);
}
public function getPanelTypeName() {
return $this->getPanel()->getPanelTypeName();
}
public function getDisplayName() {
return $this->getPanel()->getDisplayName($this);
}
public function getSortKey() {
$order = $this->getPanelOrder();
if ($order === null) {
$order = 'Z';
} else {
$order = sprintf('%020d', $order);
}
return sprintf(
'~%s%020d',
$order,
$this->getID());
}
public function isDisabled() {
return ($this->getVisibility() === self::VISIBILITY_DISABLED);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getProfileObject()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
return array(
array(
$this->getProfileObject(),
$capability,
),
);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorProfilePanelEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorProfilePanelConfigurationTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

View file

@ -0,0 +1,22 @@
<?php
final class PhabricatorProfilePanelConfigurationTransaction
extends PhabricatorApplicationTransaction {
const TYPE_PROPERTY = 'profilepanel.property';
const TYPE_ORDER = 'profilepanel.order';
const TYPE_VISIBILITY = 'profilepanel.visibility';
public function getApplicationName() {
return 'search';
}
public function getApplicationTransactionType() {
return PhabricatorProfilePanelPHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
}

View file

@ -21,6 +21,7 @@ final class PhabricatorSearchDatasource
new PhabricatorProjectDatasource(), new PhabricatorProjectDatasource(),
new PhabricatorApplicationDatasource(), new PhabricatorApplicationDatasource(),
new PhabricatorTypeaheadMonogramDatasource(), new PhabricatorTypeaheadMonogramDatasource(),
new DiffusionRepositoryDatasource(),
new DiffusionSymbolDatasource(), new DiffusionSymbolDatasource(),
); );
} }

View file

@ -16,6 +16,10 @@ final class PhabricatorEditEngineConfigurationListController
$engine = PhabricatorEditEngine::getByKey($viewer, $engine_key) $engine = PhabricatorEditEngine::getByKey($viewer, $engine_key)
->setViewer($viewer); ->setViewer($viewer);
if (!$engine->isEngineConfigurable()) {
return new Aphront404Response();
}
$items = array(); $items = array();
$items[] = id(new PHUIListItemView()) $items[] = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LABEL) ->setType(PHUIListItemView::TYPE_LABEL)

View file

@ -72,6 +72,10 @@ abstract class PhabricatorEditEngineController
$engine = $config->getEngine(); $engine = $config->getEngine();
} }
if (!$engine->isEngineConfigurable()) {
return null;
}
return $config; return $config;
} }

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