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:
commit
eac1124bd9
119 changed files with 3303 additions and 2995 deletions
105
externals/s3/README.txt
vendored
105
externals/s3/README.txt
vendored
|
@ -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 PHP’s 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
2317
externals/s3/S3.php
vendored
File diff suppressed because it is too large
Load diff
|
@ -7,11 +7,11 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => '3ea6dc33',
|
||||
'core.pkg.js' => '57dff7df',
|
||||
'core.pkg.css' => '1eed0b4f',
|
||||
'core.pkg.js' => '6ae03393',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '2de124c9',
|
||||
'differential.pkg.js' => '64e69521',
|
||||
'differential.pkg.js' => 'f83532f8',
|
||||
'diffusion.pkg.css' => 'f45955ed',
|
||||
'diffusion.pkg.js' => '3a9a8bfa',
|
||||
'maniphest.pkg.css' => '4845691a',
|
||||
|
@ -102,7 +102,7 @@ return array(
|
|||
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
|
||||
'rsrc/css/application/uiexample/example.css' => '528b19de',
|
||||
'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/z-index.css' => '57ddcaa2',
|
||||
'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-query-panel-select.js' => '453c5375',
|
||||
'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/behavior-add-reviewers-and-ccs.js' => 'e10f8e18',
|
||||
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
|
||||
'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76',
|
||||
'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-keyboard-nav.js' => '2c426492',
|
||||
'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-typeahead.js' => 'de2e896f',
|
||||
'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/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-fields.js' => 'b59e1e96',
|
||||
'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-reveal-content.js' => '60821bc7',
|
||||
'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-time-typeahead.js' => 'f80d6bf0',
|
||||
'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33',
|
||||
|
@ -523,7 +524,7 @@ return array(
|
|||
'aphront-typeahead-control-css' => '0e403212',
|
||||
'auth-css' => '0877ed6e',
|
||||
'bulk-job-css' => 'df9c1d4a',
|
||||
'changeset-view-manager' => '58562350',
|
||||
'changeset-view-manager' => 'a2828756',
|
||||
'conduit-api-css' => '7bc725c4',
|
||||
'config-options-css' => '0ede4c9b',
|
||||
'config-welcome-css' => '6abd79be',
|
||||
|
@ -570,7 +571,7 @@ return array(
|
|||
'javelin-behavior-audit-preview' => 'd835b03a',
|
||||
'javelin-behavior-bulk-job-reload' => 'edf8a145',
|
||||
'javelin-behavior-choose-control' => '327a00d1',
|
||||
'javelin-behavior-comment-actions' => 'b65559c0',
|
||||
'javelin-behavior-comment-actions' => '1f2fcaf8',
|
||||
'javelin-behavior-config-reorder-fields' => 'b6993408',
|
||||
'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a',
|
||||
'javelin-behavior-conpherence-menu' => '1d45c74d',
|
||||
|
@ -588,7 +589,7 @@ return array(
|
|||
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
|
||||
'javelin-behavior-differential-comment-jump' => '4fdb476d',
|
||||
'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-feedback-preview' => 'b064af76',
|
||||
'javelin-behavior-differential-keyboard-navigation' => '2c426492',
|
||||
|
@ -640,7 +641,7 @@ return array(
|
|||
'javelin-behavior-phabricator-oncopy' => '2926fff2',
|
||||
'javelin-behavior-phabricator-remarkup-assist' => 'b60b6d9b',
|
||||
'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-tooltips' => '3ee3408b',
|
||||
'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6',
|
||||
|
@ -663,6 +664,7 @@ return array(
|
|||
'javelin-behavior-remarkup-preview' => '4b700e9e',
|
||||
'javelin-behavior-reorder-applications' => '76b9fc3e',
|
||||
'javelin-behavior-reorder-columns' => 'e1d25dfb',
|
||||
'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072',
|
||||
'javelin-behavior-repository-crossreference' => 'e5339c43',
|
||||
'javelin-behavior-scrollbar' => '834a1173',
|
||||
'javelin-behavior-search-reorder-queries' => 'e9581f08',
|
||||
|
@ -757,7 +759,7 @@ return array(
|
|||
'phabricator-object-selector-css' => '85ee8ce6',
|
||||
'phabricator-phtize' => 'd254d646',
|
||||
'phabricator-prefab' => '666c80c5',
|
||||
'phabricator-remarkup-css' => '7afb543c',
|
||||
'phabricator-remarkup-css' => 'b6ad82e4',
|
||||
'phabricator-search-results-css' => '7dea472c',
|
||||
'phabricator-shaped-request' => '7cbe244b',
|
||||
'phabricator-side-menu-view-css' => '91b7a42c',
|
||||
|
@ -878,16 +880,6 @@ return array(
|
|||
'javelin-behavior-device',
|
||||
'phabricator-title',
|
||||
),
|
||||
'048330fa' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-typeahead',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'05270951' => array(
|
||||
'javelin-util',
|
||||
'javelin-magical-init',
|
||||
|
@ -915,6 +907,16 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-router',
|
||||
),
|
||||
'0b7a4f6e' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-typeahead',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'0f764c35' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -969,17 +971,14 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-reactor-dom',
|
||||
),
|
||||
'2035b9cb' => array(
|
||||
'1f2fcaf8' => 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',
|
||||
'javelin-dom',
|
||||
'phuix-form-control-view',
|
||||
'phuix-icon-view',
|
||||
'javelin-behavior-phabricator-gesture',
|
||||
),
|
||||
'21ba5861' => array(
|
||||
'javelin-behavior',
|
||||
|
@ -1201,16 +1200,6 @@ return array(
|
|||
'javelin-request',
|
||||
'javelin-util',
|
||||
),
|
||||
58562350 => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-install',
|
||||
'javelin-workflow',
|
||||
'javelin-router',
|
||||
'javelin-behavior-device',
|
||||
'javelin-vector',
|
||||
),
|
||||
'59a7976a' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1562,6 +1551,18 @@ return array(
|
|||
'javelin-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(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1601,6 +1602,16 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-install',
|
||||
),
|
||||
'a2828756' => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-install',
|
||||
'javelin-workflow',
|
||||
'javelin-router',
|
||||
'javelin-behavior-device',
|
||||
'javelin-vector',
|
||||
),
|
||||
'a464fe03' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-uri',
|
||||
|
@ -1740,15 +1751,6 @@ return array(
|
|||
'javelin-workflow',
|
||||
'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(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1933,6 +1935,13 @@ return array(
|
|||
'e292eaf4' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'e2e0a072' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'javelin-dom',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'e379b58e' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
|
5
resources/sql/autopatches/20160110.repo.01.slug.sql
Normal file
5
resources/sql/autopatches/20160110.repo.01.slug.sql
Normal 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);
|
49
resources/sql/autopatches/20160110.repo.02.slug.php
Normal file
49
resources/sql/autopatches/20160110.repo.02.slug.php
Normal 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));
|
||||
}
|
||||
|
||||
}
|
2
resources/sql/autopatches/20160111.repo.01.slugx.sql
Normal file
2
resources/sql/autopatches/20160111.repo.01.slugx.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_repository.repository_transaction
|
||||
SET transactionType = 'repo:slug' WHERE transactionType = 'repo:clone-name';
|
7
resources/sql/autopatches/20160112.repo.01.uri.sql
Normal file
7
resources/sql/autopatches/20160112.repo.01.uri.sql
Normal 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};
|
7
resources/sql/autopatches/20160112.repo.02.uri.index.php
Normal file
7
resources/sql/autopatches/20160112.repo.02.uri.index.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorRepository();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $repo) {
|
||||
$repo->updateURIIndex();
|
||||
}
|
13
resources/sql/autopatches/20160113.propanel.1.storage.sql
Normal file
13
resources/sql/autopatches/20160113.propanel.1.storage.sql
Normal 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};
|
19
resources/sql/autopatches/20160113.propanel.2.xaction.sql
Normal file
19
resources/sql/autopatches/20160113.propanel.2.xaction.sql
Normal 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};
|
|
@ -249,6 +249,7 @@ phutil_register_library_map(array(
|
|||
'ConduitStringParameterType' => 'applications/conduit/parametertype/ConduitStringParameterType.php',
|
||||
'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php',
|
||||
'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php',
|
||||
'ConduitUserParameterType' => 'applications/conduit/parametertype/ConduitUserParameterType.php',
|
||||
'ConduitWildParameterType' => 'applications/conduit/parametertype/ConduitWildParameterType.php',
|
||||
'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php',
|
||||
'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php',
|
||||
|
@ -256,7 +257,6 @@ phutil_register_library_map(array(
|
|||
'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php',
|
||||
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
|
||||
'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php',
|
||||
'ConpherenceCreateThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php',
|
||||
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
|
||||
'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php',
|
||||
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
|
||||
|
@ -734,6 +734,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
|
||||
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
||||
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
||||
'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
|
||||
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
|
||||
'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
|
||||
'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
|
||||
|
@ -1200,6 +1201,7 @@ phutil_register_library_map(array(
|
|||
'HeraldTranscriptDestructionEngineExtension' => 'applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php',
|
||||
'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php',
|
||||
'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php',
|
||||
'HeraldTranscriptPHIDType' => 'applications/herald/phid/HeraldTranscriptPHIDType.php',
|
||||
'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php',
|
||||
'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php',
|
||||
'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php',
|
||||
|
@ -2418,6 +2420,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php',
|
||||
'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php',
|
||||
'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php',
|
||||
'PhabricatorLinkProfilePanel' => 'applications/search/profilepanel/PhabricatorLinkProfilePanel.php',
|
||||
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
|
||||
'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php',
|
||||
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
|
||||
|
@ -2825,6 +2828,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
|
||||
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.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',
|
||||
'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
|
||||
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
|
||||
|
@ -2855,6 +2868,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
|
||||
'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php',
|
||||
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
|
||||
'PhabricatorProjectDetailsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php',
|
||||
'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php',
|
||||
'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php',
|
||||
'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php',
|
||||
|
@ -2876,6 +2890,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
|
||||
'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php',
|
||||
'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php',
|
||||
'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php',
|
||||
'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
|
||||
'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php',
|
||||
'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
|
||||
|
@ -2885,6 +2900,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
|
||||
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
|
||||
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
|
||||
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
|
||||
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
|
||||
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
|
||||
'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
|
||||
|
@ -2908,6 +2924,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
|
||||
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
|
||||
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
|
||||
'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php',
|
||||
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
|
||||
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
|
||||
'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php',
|
||||
|
@ -3009,6 +3026,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
|
||||
'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
|
||||
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
|
||||
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
|
||||
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
|
||||
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
|
||||
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
|
||||
|
@ -4184,6 +4202,7 @@ phutil_register_library_map(array(
|
|||
'ConduitStringParameterType' => 'ConduitParameterType',
|
||||
'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'ConduitUserListParameterType' => 'ConduitListParameterType',
|
||||
'ConduitUserParameterType' => 'ConduitParameterType',
|
||||
'ConduitWildParameterType' => 'ConduitListParameterType',
|
||||
'ConpherenceColumnViewController' => 'ConpherenceController',
|
||||
'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
|
@ -4191,7 +4210,6 @@ phutil_register_library_map(array(
|
|||
'ConpherenceConstants' => 'Phobject',
|
||||
'ConpherenceController' => 'PhabricatorController',
|
||||
'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
|
||||
'ConpherenceCreateThreadMailReceiver' => 'PhabricatorMailReceiver',
|
||||
'ConpherenceDAO' => 'PhabricatorLiskDAO',
|
||||
'ConpherenceDurableColumnView' => 'AphrontTagView',
|
||||
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
|
@ -4707,6 +4725,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryTag' => 'Phobject',
|
||||
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
||||
'DiffusionRequest' => 'Phobject',
|
||||
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionResolveUserQuery' => 'Phobject',
|
||||
|
@ -5279,6 +5298,7 @@ phutil_register_library_map(array(
|
|||
'HeraldTranscriptDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
||||
'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'HeraldTranscriptListController' => 'HeraldController',
|
||||
'HeraldTranscriptPHIDType' => 'PhabricatorPHIDType',
|
||||
'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'HeraldTranscriptTestCase' => 'PhabricatorTestCase',
|
||||
|
@ -6697,6 +6717,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule',
|
||||
'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase',
|
||||
'PhabricatorLinkProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorLipsumArtist' => 'Phobject',
|
||||
'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',
|
||||
'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
|
@ -7168,6 +7189,20 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
|
||||
'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(
|
||||
'PhabricatorProjectDAO',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
|
@ -7179,6 +7214,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorFulltextInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
'PhabricatorProfilePanelInterface',
|
||||
),
|
||||
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||
|
@ -7220,6 +7256,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
|
||||
'PhabricatorProjectDetailsProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectEditController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
|
||||
|
@ -7240,6 +7277,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule',
|
||||
'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
|
||||
|
@ -7249,6 +7287,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
|
||||
'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -7275,6 +7314,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectViewController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
|
||||
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
|
||||
'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
|
||||
|
@ -7415,6 +7455,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorRepositoryType' => 'Phobject',
|
||||
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositoryURINormalizer' => 'Phobject',
|
||||
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
|
||||
|
|
|
@ -635,4 +635,19 @@ abstract class PhabricatorApplication
|
|||
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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,8 @@ final class PhabricatorStorageSetupCheck extends PhabricatorSetupCheck {
|
|||
$engines = PhabricatorFileStorageEngine::loadWritableChunkEngines();
|
||||
$chunk_engine_active = (bool)$engines;
|
||||
|
||||
$this->checkS3();
|
||||
|
||||
if (!$chunk_engine_active) {
|
||||
$doc_href = PhabricatorEnv::getDocLink('Configuring File Storage');
|
||||
|
||||
|
@ -140,4 +142,55 @@ final class PhabricatorStorageSetupCheck extends PhabricatorSetupCheck {
|
|||
->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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,14 +33,27 @@ final class PhabricatorAWSConfigOptions
|
|||
$this->newOption('amazon-s3.secret-key', 'string', null)
|
||||
->setHidden(true)
|
||||
->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)
|
||||
->setLocked(true)
|
||||
->setDescription(
|
||||
pht(
|
||||
'Explicit S3 endpoint to use. Leave empty to have Phabricator '.
|
||||
'select and endpoint. Normally, you do not need to set this.'))
|
||||
->addExample(null, pht('Use default endpoint'))
|
||||
->addExample('s3.amazon.com', pht('Use specific endpoint')),
|
||||
'Explicit S3 endpoint to use. This should be the endpoint '.
|
||||
'which corresponds to the region you have selected in '.
|
||||
'`amazon-s3.region`. Phabricator can not determine the correct '.
|
||||
'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)
|
||||
->setLocked(true)
|
||||
->setDescription(pht('Access key for Amazon EC2.')),
|
||||
|
|
|
@ -21,7 +21,14 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject {
|
|||
}
|
||||
|
||||
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()) {
|
||||
|
|
|
@ -275,20 +275,21 @@ final class PhabricatorSetupIssueView extends AphrontView {
|
|||
$update = array();
|
||||
foreach ($configs as $config) {
|
||||
if (idx($options, $config) && $options[$config]->getLocked()) {
|
||||
continue;
|
||||
$name = pht('View "%s"', $config);
|
||||
} else {
|
||||
$name = pht('Edit "%s"', $config);
|
||||
}
|
||||
$link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(),
|
||||
),
|
||||
pht('Edit %s', $config));
|
||||
$name);
|
||||
$update[] = phutil_tag('li', array(), $link);
|
||||
}
|
||||
if ($update) {
|
||||
$update = phutil_tag('ul', array(), $update);
|
||||
if (!$related) {
|
||||
|
||||
$update_info = phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -71,9 +71,15 @@ final class PhabricatorDaemonBulkJobViewController
|
|||
->setUser($viewer)
|
||||
->setObject($job);
|
||||
|
||||
if ($job->isConfirming()) {
|
||||
$continue_uri = $job->getMonitorURI();
|
||||
} else {
|
||||
$continue_uri = $job->getDoneURI();
|
||||
}
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($job->getDoneURI())
|
||||
->setHref($continue_uri)
|
||||
->setIcon('fa-arrow-circle-o-right')
|
||||
->setName(pht('Continue')));
|
||||
|
||||
|
|
|
@ -43,6 +43,26 @@ final class DifferentialCreateRawDiffConduitAPIMethod
|
|||
$changes = $parser->parseDiff($raw_diff);
|
||||
$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(
|
||||
'creationMethod' => 'web',
|
||||
'authorPHID' => $viewer->getPHID(),
|
||||
|
|
|
@ -129,8 +129,8 @@ final class DifferentialChangesetListView extends AphrontView {
|
|||
array(
|
||||
'pht' => array(
|
||||
'Open in Editor' => pht('Open in Editor'),
|
||||
'Show Entire File' => pht('Show Entire File'),
|
||||
'Entire File Shown' => pht('Entire File Shown'),
|
||||
'Show All Context' => pht('Show All Context'),
|
||||
'All Context Shown' => pht('All Context Shown'),
|
||||
"Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"),
|
||||
'Expand File' => pht('Expand File'),
|
||||
'Collapse File' => pht('Collapse File'),
|
||||
|
|
|
@ -71,7 +71,7 @@ final class DiffusionLintSaveRunner extends Phobject {
|
|||
} else if ($uuid) {
|
||||
$repository_query->withUUIDs(array($uuid));
|
||||
} else if ($remote_uri) {
|
||||
$repository_query->withRemoteURIs(array($remote_uri));
|
||||
$repository_query->withURIs(array($remote_uri));
|
||||
}
|
||||
|
||||
$repository = $repository_query->executeOne();
|
||||
|
|
|
@ -79,6 +79,7 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
|||
'repositoryPHID' => $commit->getRepository()->getPHID(),
|
||||
'identifier' => $commit->getCommitIdentifier(),
|
||||
'epoch' => $commit->getEpoch(),
|
||||
'authorEpoch' => $commit_data->getCommitDetail('authorEpoch'),
|
||||
'uri' => $uri,
|
||||
'isImporting' => !$commit->isImported(),
|
||||
'summary' => $commit->getSummary(),
|
||||
|
@ -99,6 +100,7 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
|||
->withIdentifier($commit->getCommitIdentifier())
|
||||
->execute();
|
||||
|
||||
$dict['authorEpoch'] = $lowlevel_commitref->getAuthorEpoch();
|
||||
$dict['author'] = $lowlevel_commitref->getAuthor();
|
||||
$dict['authorName'] = $lowlevel_commitref->getAuthorName();
|
||||
$dict['authorEmail'] = $lowlevel_commitref->getAuthorEmail();
|
||||
|
|
|
@ -1761,7 +1761,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
'size' => 600,
|
||||
),
|
||||
),
|
||||
$commit->getShortName());
|
||||
$commit->getLocalName());
|
||||
|
||||
$links[$identifier] = $commit_link;
|
||||
}
|
||||
|
|
|
@ -492,8 +492,12 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
|
||||
if (!$repository->isSVN()) {
|
||||
$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) {
|
||||
$authored_info->setTarget($handles[$author_phid]->renderLink());
|
||||
|
|
|
@ -17,21 +17,20 @@ final class DiffusionRepositoryEditBasicController
|
|||
|
||||
$v_name = $repository->getName();
|
||||
$v_desc = $repository->getDetail('description');
|
||||
$v_clone_name = $repository->getDetail('clone-name');
|
||||
$v_slug = $repository->getRepositorySlug();
|
||||
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$repository->getPHID(),
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
|
||||
$e_name = true;
|
||||
$e_slug = null;
|
||||
$errors = array();
|
||||
|
||||
$validation_exception = null;
|
||||
if ($request->isFormPost()) {
|
||||
$v_name = $request->getStr('name');
|
||||
$v_desc = $request->getStr('description');
|
||||
$v_projects = $request->getArr('projectPHIDs');
|
||||
|
||||
if ($repository->isHosted()) {
|
||||
$v_clone_name = $request->getStr('cloneName');
|
||||
}
|
||||
$v_slug = $request->getStr('slug');
|
||||
|
||||
if (!strlen($v_name)) {
|
||||
$e_name = pht('Required');
|
||||
|
@ -47,7 +46,7 @@ final class DiffusionRepositoryEditBasicController
|
|||
$type_name = PhabricatorRepositoryTransaction::TYPE_NAME;
|
||||
$type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
|
||||
$type_edge = PhabricatorTransactions::TYPE_EDGE;
|
||||
$type_clone_name = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME;
|
||||
$type_slug = PhabricatorRepositoryTransaction::TYPE_SLUG;
|
||||
|
||||
$xactions[] = id(clone $template)
|
||||
->setTransactionType($type_name)
|
||||
|
@ -58,8 +57,8 @@ final class DiffusionRepositoryEditBasicController
|
|||
->setNewValue($v_desc);
|
||||
|
||||
$xactions[] = id(clone $template)
|
||||
->setTransactionType($type_clone_name)
|
||||
->setNewValue($v_clone_name);
|
||||
->setTransactionType($type_slug)
|
||||
->setNewValue($v_slug);
|
||||
|
||||
$xactions[] = id(clone $template)
|
||||
->setTransactionType($type_edge)
|
||||
|
@ -71,13 +70,20 @@ final class DiffusionRepositoryEditBasicController
|
|||
'=' => array_fuse($v_projects),
|
||||
));
|
||||
|
||||
id(new PhabricatorRepositoryEditor())
|
||||
$editor = id(new PhabricatorRepositoryEditor())
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setActor($viewer)
|
||||
->applyTransactions($repository, $xactions);
|
||||
->setActor($viewer);
|
||||
|
||||
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')
|
||||
->setLabel(pht('Name'))
|
||||
->setValue($v_name)
|
||||
->setError($e_name));
|
||||
|
||||
if ($repository->isHosted()) {
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('cloneName')
|
||||
->setLabel(pht('Clone/Checkout As'))
|
||||
->setValue($v_clone_name)
|
||||
->setCaption(
|
||||
pht(
|
||||
'Optional directory name to use when cloning or checking out '.
|
||||
'this repository.')));
|
||||
}
|
||||
|
||||
$form
|
||||
->setError($e_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('slug')
|
||||
->setLabel(pht('Short Name'))
|
||||
->setValue($v_slug)
|
||||
->setError($e_slug))
|
||||
->appendChild(
|
||||
id(new PhabricatorRemarkupControl())
|
||||
->setUser($viewer)
|
||||
|
@ -130,6 +127,7 @@ final class DiffusionRepositoryEditBasicController
|
|||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setValidationException($validation_exception)
|
||||
->setForm($form)
|
||||
->setFormErrors($errors);
|
||||
|
||||
|
|
|
@ -286,15 +286,12 @@ final class DiffusionRepositoryEditMainController
|
|||
$view->addProperty(pht('Type'), $type);
|
||||
$view->addProperty(pht('Callsign'), $repository->getCallsign());
|
||||
|
||||
$clone_name = $repository->getDetail('clone-name');
|
||||
|
||||
if ($repository->isHosted()) {
|
||||
$view->addProperty(
|
||||
pht('Clone/Checkout As'),
|
||||
$clone_name
|
||||
? $clone_name.'/'
|
||||
: phutil_tag('em', array(), $repository->getCloneName().'/'));
|
||||
$short_name = $repository->getRepositorySlug();
|
||||
if ($short_name === null) {
|
||||
$short_name = $repository->getCloneName();
|
||||
$short_name = phutil_tag('em', array(), $short_name);
|
||||
}
|
||||
$view->addProperty(pht('Short Name'), $short_name);
|
||||
|
||||
$view->invokeWillRenderEvent();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
final class DiffusionCommitRef extends Phobject {
|
||||
|
||||
private $message;
|
||||
private $authorEpoch;
|
||||
private $authorName;
|
||||
private $authorEmail;
|
||||
private $committerName;
|
||||
|
@ -11,6 +12,7 @@ final class DiffusionCommitRef extends Phobject {
|
|||
|
||||
public static function newFromConduitResult(array $result) {
|
||||
$ref = id(new DiffusionCommitRef())
|
||||
->setAuthorEpoch(idx($result, 'authorEpoch'))
|
||||
->setCommitterEmail(idx($result, 'committerEmail'))
|
||||
->setCommitterName(idx($result, 'committerName'))
|
||||
->setAuthorEmail(idx($result, 'authorEmail'))
|
||||
|
@ -38,6 +40,15 @@ final class DiffusionCommitRef extends Phobject {
|
|||
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) {
|
||||
$this->committerEmail = $committer_email;
|
||||
return $this;
|
||||
|
|
|
@ -308,6 +308,7 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
$rules = null;
|
||||
$blocking_effect = null;
|
||||
$blocked_update = null;
|
||||
$blocking_xscript = null;
|
||||
foreach ($updates as $update) {
|
||||
$adapter = id(clone $adapter_template)
|
||||
->setPushLog($update);
|
||||
|
@ -332,6 +333,7 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
if ($effect->getAction() == $block_action) {
|
||||
$blocking_effect = $effect;
|
||||
$blocked_update = $update;
|
||||
$blocking_xscript = $xscript;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -357,13 +359,16 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
throw new DiffusionCommitHookRejectException(
|
||||
pht(
|
||||
"This push was rejected by Herald push rule %s.\n".
|
||||
"Change: %s\n".
|
||||
" Rule: %s\n".
|
||||
"Reason: %s",
|
||||
" Change: %s\n".
|
||||
" Rule: %s\n".
|
||||
" Reason: %s\n".
|
||||
"Transcript: %s",
|
||||
$rule->getMonogram(),
|
||||
$blocked_name,
|
||||
$rule->getName(),
|
||||
$message));
|
||||
$message,
|
||||
PhabricatorEnv::getProductionURI(
|
||||
'/herald/transcript/'.$blocking_xscript->getID().'/')));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -52,7 +52,7 @@ final class DiffusionLowLevelCommitQuery
|
|||
'UTF-8',
|
||||
implode(
|
||||
'%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);
|
||||
|
||||
$parts = explode("\0", $info);
|
||||
|
@ -77,13 +77,19 @@ final class DiffusionLowLevelCommitQuery
|
|||
->setHashValue($parts[4]),
|
||||
);
|
||||
|
||||
$author_epoch = (int)$parts[5];
|
||||
if (!$author_epoch) {
|
||||
$author_epoch = null;
|
||||
}
|
||||
|
||||
return id(new DiffusionCommitRef())
|
||||
->setCommitterName($parts[0])
|
||||
->setCommitterEmail($parts[1])
|
||||
->setAuthorName($parts[2])
|
||||
->setAuthorEmail($parts[3])
|
||||
->setHashes($hashes)
|
||||
->setMessage($parts[5]);
|
||||
->setAuthorEpoch($author_epoch)
|
||||
->setMessage($parts[6]);
|
||||
}
|
||||
|
||||
private function loadMercurialCommitRef() {
|
||||
|
|
|
@ -24,13 +24,31 @@ final class DiffusionRepositoryDatasource
|
|||
->withDatasourceQuery($raw_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();
|
||||
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())
|
||||
->setName($repo->getMonogram().' '.$repo->getName())
|
||||
->setName($name)
|
||||
->setDisplayName($display_name)
|
||||
->setURI($repo->getURI())
|
||||
->setPHID($repo->getPHID())
|
||||
->setPriorityString($repo->getMonogram());
|
||||
->setPriorityString($repo->getMonogram())
|
||||
->setPriorityType('repo')
|
||||
->setImageSprite($image_sprite)
|
||||
->setDisplayType(pht('Repository'));
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
|
|
@ -128,7 +128,7 @@ abstract class DiffusionView extends AphrontView {
|
|||
$commit,
|
||||
$summary = '') {
|
||||
|
||||
$commit_name = $repository->formatCommitName($commit);
|
||||
$commit_name = $repository->formatCommitName($commit, $local = true);
|
||||
|
||||
if (strlen($summary)) {
|
||||
$commit_name .= ': '.$summary;
|
||||
|
|
|
@ -35,7 +35,7 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication {
|
|||
return array(
|
||||
'/diviner/' => array(
|
||||
'' => 'DivinerMainController',
|
||||
'query/((?<key>[^/]+)/)?' => 'DivinerAtomListController',
|
||||
'query/((?<queryKey>[^/]+)/)?' => 'DivinerAtomListController',
|
||||
'find/' => 'DivinerFindController',
|
||||
),
|
||||
'/book/(?P<book>[^/]+)/' => 'DivinerBookController',
|
||||
|
|
|
@ -7,14 +7,9 @@ final class DivinerAtomListController extends DivinerController {
|
|||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$query_key = $request->getURIData('key');
|
||||
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($query_key)
|
||||
->setSearchEngine(new DivinerAtomSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
return id(new DivinerAtomSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,19 +2,9 @@
|
|||
|
||||
abstract class DivinerController extends PhabricatorController {
|
||||
|
||||
protected function buildSideNavView() {
|
||||
$menu = $this->buildApplicationMenu();
|
||||
return AphrontSideNavFilterView::newFromMenu($menu);
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
$menu = new PHUIListView();
|
||||
|
||||
id(new DivinerAtomSearchEngine())
|
||||
->setViewer($this->getRequest()->getViewer())
|
||||
->addNavigationItems($menu);
|
||||
|
||||
return $menu;
|
||||
return $this->newApplicationMenu()
|
||||
->setSearchEngine(new DivinerAtomSearchEngine());
|
||||
}
|
||||
|
||||
protected function renderAtomList(array $symbols) {
|
||||
|
|
|
@ -103,7 +103,7 @@ final class DivinerAtomSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
|
||||
protected function getBuiltinQueryNames() {
|
||||
return array(
|
||||
'all' => pht('All'),
|
||||
'all' => pht('All Atoms'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
|
|||
return array(
|
||||
'/F(?P<id>[1-9]\d*)' => 'PhabricatorFileInfoController',
|
||||
'/file/' => array(
|
||||
'(query/(?P<key>[^/]+)/)?' => 'PhabricatorFileListController',
|
||||
'(query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFileListController',
|
||||
'upload/' => 'PhabricatorFileUploadController',
|
||||
'dropupload/' => 'PhabricatorFileDropUploadController',
|
||||
'compose/' => 'PhabricatorFileComposeController',
|
||||
|
|
|
@ -2,28 +2,9 @@
|
|||
|
||||
abstract class PhabricatorFileController extends PhabricatorController {
|
||||
|
||||
protected function buildSideNavView() {
|
||||
$menu = $this->buildMenu($for_devices = false);
|
||||
return AphrontSideNavFilterView::newFromMenu($menu);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -11,16 +11,14 @@ final class PhabricatorFileListController extends PhabricatorFileController {
|
|||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($request->getURIData('key'))
|
||||
->setSearchEngine(new PhabricatorFileSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
return id(new PhabricatorFileSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Upload File'))
|
||||
|
|
|
@ -28,8 +28,14 @@ final class PhabricatorS3FileStorageEngine
|
|||
$bucket = PhabricatorEnv::getEnvConfig('storage.s3.bucket');
|
||||
$access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-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',
|
||||
'method' => 'putObject',
|
||||
));
|
||||
$s3->putObject(
|
||||
$data,
|
||||
$this->getBucketName(),
|
||||
$name,
|
||||
$acl = 'private');
|
||||
|
||||
$s3
|
||||
->setParametersForPutObject($name, $data)
|
||||
->resolve();
|
||||
|
||||
$profiler->endServiceCall($call_id, array());
|
||||
|
||||
return $name;
|
||||
|
@ -84,24 +90,21 @@ final class PhabricatorS3FileStorageEngine
|
|||
*/
|
||||
public function readFile($handle) {
|
||||
$s3 = $this->newS3API();
|
||||
|
||||
$profiler = PhutilServiceProfiler::getInstance();
|
||||
$call_id = $profiler->beginServiceCall(
|
||||
array(
|
||||
'type' => 's3',
|
||||
'method' => 'getObject',
|
||||
));
|
||||
$result = $s3->getObject(
|
||||
$this->getBucketName(),
|
||||
$handle);
|
||||
|
||||
$result = $s3
|
||||
->setParametersForGetObject($handle)
|
||||
->resolve();
|
||||
|
||||
$profiler->endServiceCall($call_id, array());
|
||||
|
||||
// NOTE: The implementation of the API that we're using may respond with
|
||||
// a successful result that has length 0 and no body property.
|
||||
if (isset($result->body)) {
|
||||
return $result->body;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
@ -109,17 +112,20 @@ final class PhabricatorS3FileStorageEngine
|
|||
* Delete a blob from Amazon S3.
|
||||
*/
|
||||
public function deleteFile($handle) {
|
||||
AphrontWriteGuard::willWrite();
|
||||
$s3 = $this->newS3API();
|
||||
|
||||
AphrontWriteGuard::willWrite();
|
||||
$profiler = PhutilServiceProfiler::getInstance();
|
||||
$call_id = $profiler->beginServiceCall(
|
||||
array(
|
||||
'type' => 's3',
|
||||
'method' => 'deleteObject',
|
||||
));
|
||||
$s3->deleteObject(
|
||||
$this->getBucketName(),
|
||||
$handle);
|
||||
|
||||
$s3
|
||||
->setParametersForDeleteObject($handle)
|
||||
->resolve();
|
||||
|
||||
$profiler->endServiceCall($call_id, array());
|
||||
}
|
||||
|
||||
|
@ -147,33 +153,19 @@ final class PhabricatorS3FileStorageEngine
|
|||
* Create a new S3 API object.
|
||||
*
|
||||
* @task internal
|
||||
* @phutil-external-symbol class S3
|
||||
*/
|
||||
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');
|
||||
$secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key');
|
||||
$region = PhabricatorEnv::getEnvConfig('amazon-s3.region');
|
||||
$endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint');
|
||||
|
||||
if (!$access_key || !$secret_key) {
|
||||
throw new PhabricatorFileStorageConfigurationException(
|
||||
pht(
|
||||
"Specify '%s' and '%s'!",
|
||||
'amazon-s3.access-key',
|
||||
'amazon-s3.secret-key'));
|
||||
}
|
||||
|
||||
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;
|
||||
return id(new PhutilAWSS3Future())
|
||||
->setAccessKey($access_key)
|
||||
->setSecretKey(new PhutilOpaqueEnvelope($secret_key))
|
||||
->setRegion($region)
|
||||
->setEndpoint($endpoint)
|
||||
->setBucket($this->getBucketName());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,26 +4,29 @@ final class PhabricatorFileUploadException extends Exception {
|
|||
|
||||
public function __construct($code) {
|
||||
$map = array(
|
||||
'UPLOAD_ERR_INI_SIZE' => pht(
|
||||
UPLOAD_ERR_INI_SIZE => pht(
|
||||
"Uploaded file is too large: current limit is %s. To adjust ".
|
||||
"this limit change '%s' in php.ini.",
|
||||
ini_get('upload_max_filesize'),
|
||||
'upload_max_filesize'),
|
||||
'UPLOAD_ERR_FORM_SIZE' => pht(
|
||||
UPLOAD_ERR_FORM_SIZE => pht(
|
||||
'File is too large.'),
|
||||
'UPLOAD_ERR_PARTIAL' => pht(
|
||||
UPLOAD_ERR_PARTIAL => pht(
|
||||
'File was only partially transferred, upload did not complete.'),
|
||||
'UPLOAD_ERR_NO_FILE' => pht(
|
||||
UPLOAD_ERR_NO_FILE => pht(
|
||||
'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.'),
|
||||
'UPLOAD_ERR_CANT_WRITE' => pht(
|
||||
UPLOAD_ERR_CANT_WRITE => pht(
|
||||
'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.'),
|
||||
);
|
||||
|
||||
$message = idx($map, $code, pht('Upload failed: unknown error.'));
|
||||
$message = idx(
|
||||
$map,
|
||||
$code,
|
||||
pht('Upload failed: unknown error (%s).', $code));
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,20 @@ final class PhabricatorFilesManagementMigrateWorkflow
|
|||
'name' => 'dry-run',
|
||||
'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(
|
||||
'name' => 'all',
|
||||
'help' => pht('Migrate all files.'),
|
||||
|
@ -30,10 +44,8 @@ final class PhabricatorFilesManagementMigrateWorkflow
|
|||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
$engine_id = $args->getArg('engine');
|
||||
if (!$engine_id) {
|
||||
$target_key = $args->getArg('engine');
|
||||
if (!$target_key) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify an engine to migrate to with `%s`. '.
|
||||
|
@ -42,7 +54,7 @@ final class PhabricatorFilesManagementMigrateWorkflow
|
|||
'files engines'));
|
||||
}
|
||||
|
||||
$engine = PhabricatorFile::buildEngine($engine_id);
|
||||
$target_engine = PhabricatorFile::buildEngine($target_key);
|
||||
|
||||
$iterator = $this->buildIterator($args);
|
||||
if (!$iterator) {
|
||||
|
@ -55,63 +67,147 @@ final class PhabricatorFilesManagementMigrateWorkflow
|
|||
|
||||
$is_dry_run = $args->getArg('dry-run');
|
||||
|
||||
$min_size = (int)$args->getArg('min-size');
|
||||
$max_size = (int)$args->getArg('max-size');
|
||||
|
||||
$failed = array();
|
||||
|
||||
$engines = PhabricatorFileStorageEngine::loadAllEngines();
|
||||
$total_bytes = 0;
|
||||
$total_files = 0;
|
||||
foreach ($iterator as $file) {
|
||||
$fid = 'F'.$file->getID();
|
||||
$monogram = $file->getMonogram();
|
||||
|
||||
if ($file->getStorageEngine() == $engine_id) {
|
||||
$console->writeOut(
|
||||
$engine_key = $file->getStorageEngine();
|
||||
$engine = idx($engines, $engine_key);
|
||||
|
||||
if (!$engine) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
"%s: Already stored on '%s'",
|
||||
$fid,
|
||||
$engine_id));
|
||||
'%s: Uses unknown storage engine "%s".',
|
||||
$monogram,
|
||||
$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;
|
||||
}
|
||||
|
||||
if ($is_dry_run) {
|
||||
$console->writeOut(
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
"%s: Would migrate from '%s' to '%s' (dry run)",
|
||||
$fid,
|
||||
$file->getStorageEngine(),
|
||||
$engine_id));
|
||||
continue;
|
||||
'%s: (%s) Would migrate from "%s" to "%s" (dry run)...',
|
||||
$monogram,
|
||||
phutil_format_bytes($byte_size),
|
||||
$engine_key,
|
||||
$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 {
|
||||
$file->migrateToEngine($engine);
|
||||
$console->writeOut("%s\n", pht('Done.'));
|
||||
if ($is_dry_run) {
|
||||
// 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) {
|
||||
$console->writeOut("%s\n", pht('Failed!'));
|
||||
$console->writeErr("%s\n", (string)$ex);
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Failed! %s', (string)$ex));
|
||||
$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) {
|
||||
$console->writeOut("**%s**\n", pht('Failures!'));
|
||||
$ids = array();
|
||||
foreach ($failed as $file) {
|
||||
$ids[] = 'F'.$file->getID();
|
||||
}
|
||||
$console->writeOut("%s\n", implode(', ', $ids));
|
||||
$monograms = mpull($failed, 'getMonogram');
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Failures: %s.', implode(', ', $monograms)));
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
$console->writeOut("**%s**\n", pht('Success!'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ final class PhabricatorEmbedFileRemarkupRule
|
|||
require_celerity_resource('lightbox-attachment-css');
|
||||
|
||||
$attrs = array();
|
||||
$image_class = null;
|
||||
$image_class = 'phabricator-remarkup-embed-image';
|
||||
|
||||
$use_size = true;
|
||||
if (!$options['size']) {
|
||||
|
@ -117,8 +117,6 @@ final class PhabricatorEmbedFileRemarkupRule
|
|||
$attrs['width'] = $x;
|
||||
$attrs['height'] = $y;
|
||||
}
|
||||
|
||||
$image_class = 'phabricator-remarkup-embed-image';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
42
src/applications/herald/phid/HeraldTranscriptPHIDType.php
Normal file
42
src/applications/herald/phid/HeraldTranscriptPHIDType.php
Normal 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}/");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ final class HeraldTranscriptQuery
|
|||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $objectPHIDs;
|
||||
private $needPartialRecords;
|
||||
|
||||
|
@ -12,6 +13,11 @@ final class HeraldTranscriptQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withObjectPHIDs(array $phids) {
|
||||
$this->objectPHIDs = $phids;
|
||||
return $this;
|
||||
|
@ -95,6 +101,13 @@ final class HeraldTranscriptQuery
|
|||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
|
|
@ -190,7 +190,8 @@ final class HeraldTranscript extends HeraldDAO
|
|||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID('HLXS');
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
HeraldTranscriptPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
|
|
@ -116,7 +116,15 @@ final class ManiphestTaskPriority extends ManiphestConstants {
|
|||
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) {
|
||||
if (!ctype_digit((string)$key)) {
|
||||
throw new Exception(
|
||||
|
|
|
@ -829,6 +829,33 @@ final class ManiphestTransactionEditor
|
|||
last($with_effect));
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -327,9 +327,18 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
|
|||
// really be all the headers. It would be nice to pass the raw headers
|
||||
// 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();
|
||||
foreach ($this->headers as $key => $value) {
|
||||
$headers[] = pht('%s: %s', $key, $value);
|
||||
foreach ($this->headers as $key => $values) {
|
||||
if (!is_array($values)) {
|
||||
$values = array($values);
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$headers[] = pht('%s: %s', $key, $value);
|
||||
}
|
||||
}
|
||||
$headers = implode("\n", $headers);
|
||||
|
||||
|
|
|
@ -285,6 +285,76 @@ final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
|
|||
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
|
||||
* policies.
|
||||
|
@ -430,10 +500,12 @@ final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
|
|||
$object->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
));
|
||||
$object->setPolicies(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW => $policy,
|
||||
PhabricatorPolicyCapability::CAN_EDIT => $policy,
|
||||
));
|
||||
|
||||
return $object;
|
||||
|
|
|
@ -321,7 +321,7 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||
$objects_in = array();
|
||||
foreach ($structs as $struct) {
|
||||
$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
|
||||
// pass, we don't need to do any tests on it.
|
||||
continue;
|
||||
|
@ -335,8 +335,8 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||
// We weren't able to load the corresponding object, so just
|
||||
// reject this result outright.
|
||||
|
||||
$reject = $extended_objects[$key];
|
||||
unset($extended_objects[$key]);
|
||||
$reject = $extended_objects[$extended_key];
|
||||
unset($extended_objects[$extended_key]);
|
||||
|
||||
// TODO: This could be friendlier.
|
||||
$this->rejectObject($reject, false, '<bad-ref>');
|
||||
|
|
|
@ -61,6 +61,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
=> 'PhabricatorProjectEditPictureController',
|
||||
$this->getEditRoutePattern('edit/')
|
||||
=> 'PhabricatorProjectEditController',
|
||||
'(?P<projectID>[1-9]\d*)/panel/'
|
||||
=> $this->getPanelRouting('PhabricatorProjectPanelController'),
|
||||
'subprojects/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectSubprojectsController',
|
||||
'milestones/(?P<id>[1-9]\d*)/'
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
abstract class PhabricatorProjectBoardController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function buildIconNavView(PhabricatorProject $project) {
|
||||
$id = $project->getID();
|
||||
$nav = parent::buildIconNavView($project);
|
||||
$nav->selectFilter("board/{$id}/");
|
||||
$nav->addClass('project-board-nav');
|
||||
return $nav;
|
||||
protected function getProfileMenu() {
|
||||
$menu = parent::getProfileMenu();
|
||||
|
||||
$menu->selectFilter(PhabricatorProject::PANEL_WORKBOARD);
|
||||
$menu->addClass('project-board-nav');
|
||||
|
||||
return $menu;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -384,7 +384,7 @@ final class PhabricatorProjectBoardViewController
|
|||
->appendChild($board)
|
||||
->addClass('project-board-wrapper');
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav = $this->getProfileMenu();
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(pht('%s Board', $project->getName()))
|
||||
|
|
|
@ -49,15 +49,16 @@ final class PhabricatorProjectColumnDetailController
|
|||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->appendChild($box);
|
||||
$nav->appendChild($timeline);
|
||||
$nav = $this->getProfileMenu();
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setNavigation($nav)
|
||||
->appendChild(
|
||||
array(
|
||||
$box,
|
||||
$timeline,
|
||||
));
|
||||
}
|
||||
|
||||
private function buildHeaderView(PhabricatorProjectColumn $column) {
|
||||
|
|
|
@ -144,13 +144,11 @@ final class PhabricatorProjectColumnEditController
|
|||
->setValidationException($validation_exception)
|
||||
->setForm($form);
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->appendChild($form_box);
|
||||
$nav = $this->getProfileMenu();
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setNavigation($nav)
|
||||
->appendChild($form_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
abstract class PhabricatorProjectController extends PhabricatorController {
|
||||
|
||||
private $project;
|
||||
private $profileMenu;
|
||||
|
||||
protected function setProject(PhabricatorProject $project) {
|
||||
$this->project = $project;
|
||||
|
@ -17,7 +18,10 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
$viewer = $this->getViewer();
|
||||
$request = $this->getRequest();
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
$id = nonempty(
|
||||
$request->getURIData('projectID'),
|
||||
$request->getURIData('id'));
|
||||
|
||||
$slug = $request->getURIData('slug');
|
||||
|
||||
if ($slug) {
|
||||
|
@ -80,101 +84,33 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
}
|
||||
|
||||
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) {
|
||||
$project = $this->getProject();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$id = null;
|
||||
if ($for_app) {
|
||||
protected function getProfileMenu() {
|
||||
if (!$this->profileMenu) {
|
||||
$project = $this->getProject();
|
||||
if ($project) {
|
||||
$id = $project->getID();
|
||||
$nav->addFilter("profile/{$id}/", pht('Profile'));
|
||||
$nav->addFilter("board/{$id}/", pht('Workboard'));
|
||||
$nav->addFilter("members/{$id}/", pht('Members'));
|
||||
$nav->addFilter("feed/{$id}/", pht('Feed'));
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$engine = id(new PhabricatorProfilePanelEngine())
|
||||
->setViewer($viewer)
|
||||
->setProfileObject($project);
|
||||
|
||||
$this->profileMenu = $engine->buildNavigation();
|
||||
}
|
||||
$nav->addFilter('create', pht('Create Project'));
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
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;
|
||||
return $this->profileMenu;
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
|
|
|
@ -21,6 +21,8 @@ final class PhabricatorProjectEditPictureController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setProject($project);
|
||||
|
||||
$edit_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'))
|
||||
->setForm($upload_form);
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->selectFilter("edit/{$id}/");
|
||||
$nav->appendChild($form_box);
|
||||
$nav->appendChild($upload_box);
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_PROFILE);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setNavigation($nav)
|
||||
->appendChild(
|
||||
array(
|
||||
$form_box,
|
||||
$upload_box,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ final class PhabricatorProjectFeedController
|
|||
->setHeaderText(pht('Project Activity'))
|
||||
->appendChild($feed);
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->selectFilter("feed/{$id}/");
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter('feed');
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Feed'));
|
||||
|
|
|
@ -8,19 +8,9 @@ final class PhabricatorProjectListController
|
|||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$query_key = $request->getURIData('queryKey');
|
||||
|
||||
$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();
|
||||
return id(new PhabricatorProjectSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
|
|
|
@ -17,6 +17,8 @@ final class PhabricatorProjectMembersEditController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setProject($project);
|
||||
|
||||
$member_phids = $project->getMemberPHIDs();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
|
@ -95,8 +97,8 @@ final class PhabricatorProjectMembersEditController
|
|||
|
||||
$member_list = $this->renderMemberList($project, $handles);
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->selectFilter("members/{$id}/");
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_MEMBERS);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Members'));
|
||||
|
|
|
@ -76,8 +76,8 @@ final class PhabricatorProjectMilestonesController
|
|||
->setProjects($milestones)
|
||||
->renderList());
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->selectFilter("milestones/{$id}/");
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_MILESTONES);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Milestones'));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -43,8 +43,8 @@ final class PhabricatorProjectProfileController
|
|||
new PhabricatorProjectTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->selectFilter("profile/{$id}/");
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_PROFILE);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
||||
|
|
|
@ -75,8 +75,8 @@ final class PhabricatorProjectSubprojectsController
|
|||
->setProjects($subprojects)
|
||||
->renderList());
|
||||
|
||||
$nav = $this->buildIconNavView($project);
|
||||
$nav->selectFilter("subprojects/{$id}/");
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_SUBPROJECTS);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Subprojects'));
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -32,7 +32,7 @@ final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule {
|
|||
// controlling and these names should parse correctly.
|
||||
|
||||
// These characters may never appear anywhere in a hashtag.
|
||||
$never = '\s?!,:;{}#\\(\\)"\'';
|
||||
$never = '\s?!,:;{}#\\(\\)"\'\\*/~';
|
||||
|
||||
// These characters may not appear at the edge of the string.
|
||||
$never_edge = '.';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -10,7 +10,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorFulltextInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
PhabricatorConduitResultInterface,
|
||||
PhabricatorProfilePanelInterface {
|
||||
|
||||
protected $name;
|
||||
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
||||
|
@ -49,6 +50,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
|
||||
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) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
|
@ -644,4 +651,47 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,10 +63,16 @@ final class PhabricatorProjectLogicalViewerDatasource
|
|||
$phids = mpull($projects, 'getPHID');
|
||||
|
||||
$results = array();
|
||||
foreach ($phids as $phid) {
|
||||
if ($phids) {
|
||||
foreach ($phids as $phid) {
|
||||
$results[] = new PhabricatorQueryConstraint(
|
||||
PhabricatorQueryConstraint::OPERATOR_OR,
|
||||
$phid);
|
||||
}
|
||||
} else {
|
||||
$results[] = new PhabricatorQueryConstraint(
|
||||
PhabricatorQueryConstraint::OPERATOR_OR,
|
||||
$phid);
|
||||
PhabricatorQueryConstraint::OPERATOR_EMPTY,
|
||||
null);
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
|
|
@ -10,12 +10,7 @@ final class ReleephDefaultFieldSelector extends ReleephFieldSelector {
|
|||
* as possible. This obivously is an abomination. -epriestley
|
||||
*/
|
||||
public static function isFacebook() {
|
||||
try {
|
||||
class_exists('ReleephFacebookKarmaFieldSpecification');
|
||||
return true;
|
||||
} catch (Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
return class_exists('ReleephFacebookKarmaFieldSpecification');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,7 +63,7 @@ final class RepositoryQueryConduitAPIMethod
|
|||
|
||||
$remote_uris = $request->getValue('remoteURIs', array());
|
||||
if ($remote_uris) {
|
||||
$query->withRemoteURIs($remote_uris);
|
||||
$query->withURIs($remote_uris);
|
||||
}
|
||||
|
||||
$uuids = $request->getValue('uuids', array());
|
||||
|
|
|
@ -39,7 +39,7 @@ final class PhabricatorRepositoryEditor
|
|||
$types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_SLUG;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES;
|
||||
|
@ -98,8 +98,8 @@ final class PhabricatorRepositoryEditor
|
|||
return $object->getCredentialPHID();
|
||||
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
|
||||
return $object->shouldAllowDangerousChanges();
|
||||
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
|
||||
return $object->getDetail('clone-name');
|
||||
case PhabricatorRepositoryTransaction::TYPE_SLUG:
|
||||
return $object->getRepositorySlug();
|
||||
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
|
||||
return $object->getAlmanacServicePHID();
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
|
||||
|
@ -141,13 +141,18 @@ final class PhabricatorRepositoryEditor
|
|||
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
|
||||
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
|
||||
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
|
||||
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
|
||||
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorRepositoryTransaction::TYPE_SLUG:
|
||||
$name = $xaction->getNewValue();
|
||||
if (strlen($name)) {
|
||||
return $name;
|
||||
}
|
||||
return null;
|
||||
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
|
||||
return (int)$xaction->getNewValue();
|
||||
|
@ -215,8 +220,8 @@ final class PhabricatorRepositoryEditor
|
|||
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
|
||||
$object->setDetail('allow-dangerous-changes', $xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
|
||||
$object->setDetail('clone-name', $xaction->getNewValue());
|
||||
case PhabricatorRepositoryTransaction::TYPE_SLUG:
|
||||
$object->setRepositorySlug($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
|
||||
$object->setAlmanacServicePHID($xaction->getNewValue());
|
||||
|
@ -326,7 +331,7 @@ final class PhabricatorRepositoryEditor
|
|||
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
|
||||
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
|
||||
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
|
||||
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SLUG:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
|
||||
|
@ -448,9 +453,73 @@ final class PhabricatorRepositoryEditor
|
|||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ final class PhabricatorRepositoryRepositoryPHIDType
|
|||
}
|
||||
|
||||
public function getTypeIcon() {
|
||||
return 'fa-database';
|
||||
return 'fa-code';
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
|
|
|
@ -9,13 +9,15 @@ final class PhabricatorRepositoryQuery
|
|||
private $types;
|
||||
private $uuids;
|
||||
private $nameContains;
|
||||
private $remoteURIs;
|
||||
private $uris;
|
||||
private $datasourceQuery;
|
||||
private $slugs;
|
||||
|
||||
private $numericIdentifiers;
|
||||
private $callsignIdentifiers;
|
||||
private $phidIdentifiers;
|
||||
private $monogramIdentifiers;
|
||||
private $slugIdentifiers;
|
||||
|
||||
private $identifierMap;
|
||||
|
||||
|
@ -55,26 +57,38 @@ final class PhabricatorRepositoryQuery
|
|||
$callsigns = array();
|
||||
$phids = array();
|
||||
$monograms = array();
|
||||
$slugs = array();
|
||||
|
||||
foreach ($identifiers as $identifier) {
|
||||
if (ctype_digit((string)$identifier)) {
|
||||
$ids[$identifier] = $identifier;
|
||||
} else if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) {
|
||||
$monograms[$identifier] = $identifier;
|
||||
} else {
|
||||
$repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST;
|
||||
if (phid_get_type($identifier) === $repository_type) {
|
||||
$phids[$identifier] = $identifier;
|
||||
} else {
|
||||
$callsigns[$identifier] = $identifier;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
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->callsignIdentifiers = $callsigns;
|
||||
$this->phidIdentifiers = $phids;
|
||||
$this->monogramIdentifiers = $monograms;
|
||||
$this->slugIdentifiers = $slugs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -104,8 +118,8 @@ final class PhabricatorRepositoryQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withRemoteURIs(array $uris) {
|
||||
$this->remoteURIs = $uris;
|
||||
public function withURIs(array $uris) {
|
||||
$this->uris = $uris;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -114,6 +128,11 @@ final class PhabricatorRepositoryQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withSlugs(array $slugs) {
|
||||
$this->slugs = $slugs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needCommitCounts($need_counts) {
|
||||
$this->needCommitCounts = $need_counts;
|
||||
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
|
||||
if ($this->numericIdentifiers) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -406,6 +434,8 @@ final class PhabricatorRepositoryQuery
|
|||
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$parts = parent::buildSelectClauseParts($conn);
|
||||
|
||||
$parts[] = 'r.*';
|
||||
|
||||
if ($this->shouldJoinSummaryTable()) {
|
||||
$parts[] = 's.*';
|
||||
}
|
||||
|
@ -423,9 +453,28 @@ final class PhabricatorRepositoryQuery
|
|||
PhabricatorRepository::TABLE_SUMMARY);
|
||||
}
|
||||
|
||||
if ($this->shouldJoinURITable()) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'LEFT JOIN %T uri ON r.phid = uri.repositoryPHID',
|
||||
id(new PhabricatorRepositoryURIIndex())->getTableName());
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
protected function shouldGroupQueryResultRows() {
|
||||
if ($this->shouldJoinURITable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldGroupQueryResultRows();
|
||||
}
|
||||
|
||||
private function shouldJoinURITable() {
|
||||
return ($this->uris !== null);
|
||||
}
|
||||
|
||||
private function shouldJoinSummaryTable() {
|
||||
if ($this->needCommitCounts) {
|
||||
return true;
|
||||
|
@ -474,7 +523,8 @@ final class PhabricatorRepositoryQuery
|
|||
if ($this->numericIdentifiers ||
|
||||
$this->callsignIdentifiers ||
|
||||
$this->phidIdentifiers ||
|
||||
$this->monogramIdentifiers) {
|
||||
$this->monogramIdentifiers ||
|
||||
$this->slugIdentifiers) {
|
||||
$identifier_clause = array();
|
||||
|
||||
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).')');
|
||||
}
|
||||
|
||||
|
@ -545,7 +602,7 @@ final class PhabricatorRepositoryQuery
|
|||
if (strlen($this->nameContains)) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'name LIKE %~',
|
||||
'r.name LIKE %~',
|
||||
$this->nameContains);
|
||||
}
|
||||
|
||||
|
@ -559,9 +616,27 @@ final class PhabricatorRepositoryQuery
|
|||
}
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'r.name LIKE %> OR r.callsign LIKE %>',
|
||||
'r.name LIKE %> OR r.callsign LIKE %> OR r.repositorySlug LIKE %>',
|
||||
$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;
|
||||
|
@ -580,7 +655,7 @@ final class PhabricatorRepositoryQuery
|
|||
// 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.
|
||||
|
||||
foreach ($this->remoteURIs as $uri) {
|
||||
foreach ($this->uris as $uri) {
|
||||
$normalized_uris[] = new PhabricatorRepositoryURINormalizer(
|
||||
PhabricatorRepositoryURINormalizer::TYPE_GIT,
|
||||
$uri);
|
||||
|
|
|
@ -160,10 +160,16 @@ final class PhabricatorRepositorySearchEngine
|
|||
|
||||
$commit = $repository->getMostRecentCommit();
|
||||
if ($commit) {
|
||||
$commit_link = DiffusionView::linkCommit(
|
||||
$repository,
|
||||
$commit->getCommitIdentifier(),
|
||||
$commit->getSummary());
|
||||
$commit_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $commit->getURI(),
|
||||
),
|
||||
pht(
|
||||
'%s: %s',
|
||||
$commit->getLocalName(),
|
||||
$commit->getSummary()));
|
||||
|
||||
$item->setSubhead($commit_link);
|
||||
$item->setEpoch($commit->getEpoch());
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
|
||||
protected $name;
|
||||
protected $callsign;
|
||||
protected $repositorySlug;
|
||||
protected $uuid;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
|
@ -93,6 +94,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'sort255',
|
||||
'callsign' => 'sort32',
|
||||
'repositorySlug' => 'sort64?',
|
||||
'versionControlSystem' => 'text32',
|
||||
'uuid' => 'text64?',
|
||||
'pushPolicy' => 'policy',
|
||||
|
@ -100,11 +102,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
'almanacServicePHID' => 'phid?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_phid' => null,
|
||||
'phid' => array(
|
||||
'columns' => array('phid'),
|
||||
'unique' => true,
|
||||
),
|
||||
'callsign' => array(
|
||||
'columns' => array('callsign'),
|
||||
'unique' => true,
|
||||
|
@ -115,6 +112,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
'key_vcs' => array(
|
||||
'columns' => array('versionControlSystem'),
|
||||
),
|
||||
'key_slug' => array(
|
||||
'columns' => array('repositorySlug'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@ -297,7 +298,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
* @return string
|
||||
*/
|
||||
public function getCloneName() {
|
||||
$name = $this->getDetail('clone-name');
|
||||
$name = $this->getRepositorySlug();
|
||||
|
||||
// Make some reasonable effort to produce reasonable default directory
|
||||
// names from repository names.
|
||||
|
@ -314,6 +315,82 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
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 )------------------------------------------- */
|
||||
|
||||
|
@ -746,30 +823,40 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $uri;
|
||||
}
|
||||
|
||||
public function getNormalizedPath() {
|
||||
$uri = (string)$this->getCloneURIObject();
|
||||
public function updateURIIndex() {
|
||||
$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()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$normalized_uri = new PhabricatorRepositoryURINormalizer(
|
||||
return new PhabricatorRepositoryURINormalizer(
|
||||
PhabricatorRepositoryURINormalizer::TYPE_GIT,
|
||||
$uri);
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
$normalized_uri = new PhabricatorRepositoryURINormalizer(
|
||||
return new PhabricatorRepositoryURINormalizer(
|
||||
PhabricatorRepositoryURINormalizer::TYPE_SVN,
|
||||
$uri);
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$normalized_uri = new PhabricatorRepositoryURINormalizer(
|
||||
return new PhabricatorRepositoryURINormalizer(
|
||||
PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
|
||||
$uri);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unrecognized version control system.'));
|
||||
}
|
||||
|
||||
return $normalized_uri->getNormalizedPath();
|
||||
}
|
||||
|
||||
public function isTracked() {
|
||||
|
@ -847,7 +934,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $this->isBranchInFilter($branch, 'branch-filter');
|
||||
}
|
||||
|
||||
public function formatCommitName($commit_identifier) {
|
||||
public function formatCommitName($commit_identifier, $local = false) {
|
||||
$vcs = $this->getVersionControlSystem();
|
||||
|
||||
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
|
@ -856,12 +943,23 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$is_git = ($vcs == $type_git);
|
||||
$is_hg = ($vcs == $type_hg);
|
||||
if ($is_git || $is_hg) {
|
||||
$short_identifier = substr($commit_identifier, 0, 12);
|
||||
$name = substr($commit_identifier, 0, 12);
|
||||
$need_scope = false;
|
||||
} 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() {
|
||||
|
@ -2143,13 +2241,17 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
|
||||
$phid = $this->getPHID();
|
||||
|
||||
$this->openTransaction();
|
||||
|
||||
$this->delete();
|
||||
|
||||
PhabricatorRepositoryURIIndex::updateRepositoryURIs($phid, array());
|
||||
|
||||
$books = id(new DivinerBookQuery())
|
||||
->setViewer($engine->getViewer())
|
||||
->withRepositoryPHIDs(array($this->getPHID()))
|
||||
->withRepositoryPHIDs(array($phid))
|
||||
->execute();
|
||||
foreach ($books as $book) {
|
||||
$engine->destroyObject($book);
|
||||
|
@ -2157,7 +2259,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
|
||||
$atoms = id(new DivinerAtomQuery())
|
||||
->setViewer($engine->getViewer())
|
||||
->withRepositoryPHIDs(array($this->getPHID()))
|
||||
->withRepositoryPHIDs(array($phid))
|
||||
->execute();
|
||||
foreach ($atoms as $atom) {
|
||||
$engine->destroyObject($atom);
|
||||
|
|
|
@ -266,9 +266,20 @@ final class PhabricatorRepositoryCommit
|
|||
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();
|
||||
return substr($identifier, 0, 9);
|
||||
return $repository->formatCommitName($identifier, $local = true);
|
||||
}
|
||||
|
||||
public function renderAuthorLink($handles) {
|
||||
|
|
|
@ -23,7 +23,7 @@ final class PhabricatorRepositoryTransaction
|
|||
const TYPE_PUSH_POLICY = 'repo:push-policy';
|
||||
const TYPE_CREDENTIAL = 'repo:credential';
|
||||
const TYPE_DANGEROUS = 'repo:dangerous';
|
||||
const TYPE_CLONE_NAME = 'repo:clone-name';
|
||||
const TYPE_SLUG = 'repo:slug';
|
||||
const TYPE_SERVICE = 'repo:service';
|
||||
const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source';
|
||||
const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language';
|
||||
|
@ -369,19 +369,19 @@ final class PhabricatorRepositoryTransaction
|
|||
'%s enabled protection against dangerous changes.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
case self::TYPE_CLONE_NAME:
|
||||
case self::TYPE_SLUG:
|
||||
if (strlen($old) && !strlen($new)) {
|
||||
return pht(
|
||||
'%s removed the clone name of this repository.',
|
||||
'%s removed the short name of this repository.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else if (strlen($new) && !strlen($old)) {
|
||||
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),
|
||||
$new);
|
||||
} else {
|
||||
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),
|
||||
$old,
|
||||
$new);
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker
|
|||
->setMaximumBytes(255)
|
||||
->truncateString((string)$author));
|
||||
|
||||
$data->setCommitDetail('authorEpoch', $ref->getAuthorEpoch());
|
||||
$data->setCommitDetail('authorName', $ref->getAuthorName());
|
||||
$data->setCommitDetail('authorEmail', $ref->getAuthorEmail());
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ final class PhabricatorSearchHovercardController
|
|||
$cards = array();
|
||||
foreach ($phids as $phid) {
|
||||
$handle = $handles[$phid];
|
||||
$object = $objects[$phid];
|
||||
$object = idx($objects, $phid);
|
||||
|
||||
$hovercard = id(new PhabricatorHovercardView())
|
||||
->setUser($viewer)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
650
src/applications/search/engine/PhabricatorProfilePanelEngine.php
Normal file
650
src/applications/search/engine/PhabricatorProfilePanelEngine.php
Normal 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'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
interface PhabricatorProfilePanelInterface {
|
||||
|
||||
public function getBuiltinProfilePanels();
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,7 @@ final class PhabricatorSearchDatasource
|
|||
new PhabricatorProjectDatasource(),
|
||||
new PhabricatorApplicationDatasource(),
|
||||
new PhabricatorTypeaheadMonogramDatasource(),
|
||||
new DiffusionRepositoryDatasource(),
|
||||
new DiffusionSymbolDatasource(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ final class PhabricatorEditEngineConfigurationListController
|
|||
$engine = PhabricatorEditEngine::getByKey($viewer, $engine_key)
|
||||
->setViewer($viewer);
|
||||
|
||||
if (!$engine->isEngineConfigurable()) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$items = array();
|
||||
$items[] = id(new PHUIListItemView())
|
||||
->setType(PHUIListItemView::TYPE_LABEL)
|
||||
|
|
|
@ -72,6 +72,10 @@ abstract class PhabricatorEditEngineController
|
|||
$engine = $config->getEngine();
|
||||
}
|
||||
|
||||
if (!$engine->isEngineConfigurable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue